diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/unittests/helpers.py | 74 | ||||
| -rw-r--r-- | tests/unittests/test__init__.py | 92 | ||||
| -rw-r--r-- | tests/unittests/test_atomic_helper.py | 4 | ||||
| -rw-r--r-- | tests/unittests/test_data.py | 53 | ||||
| -rw-r--r-- | tests/unittests/test_datasource/test_gce.py | 4 | ||||
| -rw-r--r-- | tests/unittests/test_datasource/test_openstack.py | 11 | ||||
| -rw-r--r--[-rwxr-xr-x] | tests/unittests/test_distros/test_user_data_normalize.py | 0 | ||||
| -rw-r--r-- | tests/unittests/test_ec2_util.py | 49 | ||||
| -rw-r--r--[-rwxr-xr-x] | tests/unittests/test_net.py | 184 | ||||
| -rw-r--r-- | tests/unittests/test_sshutil.py | 24 | 
10 files changed, 341 insertions, 154 deletions
| diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index cf3b46d2..90e2431f 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -29,7 +29,6 @@ PY2 = False  PY26 = False  PY27 = False  PY3 = False -FIX_HTTPRETTY = False  _PY_VER = sys.version_info  _PY_MAJOR, _PY_MINOR, _PY_MICRO = _PY_VER[0:3] @@ -44,8 +43,6 @@ else:          PY2 = True      if (_PY_MAJOR, _PY_MINOR) >= (3, 0):          PY3 = True -        if _PY_MINOR == 4 and _PY_MICRO < 3: -            FIX_HTTPRETTY = True  # Makes the old path start @@ -86,6 +83,28 @@ class TestCase(unittest2.TestCase):      pass +class CiTestCase(TestCase): +    """This is the preferred test case base class unless user +       needs other test case classes below.""" +    def tmp_dir(self, dir=None, cleanup=True): +        # return a full path to a temporary directory that will be cleaned up. +        if dir is None: +            tmpd = tempfile.mkdtemp( +                prefix="ci-%s." % self.__class__.__name__) +        else: +            tmpd = tempfile.mkdtemp(dir=dir) +        self.addCleanup(functools.partial(shutil.rmtree, tmpd)) +        return tmpd + +    def tmp_path(self, path, dir=None): +        # return an absolute path to 'path' under dir. +        # if dir is None, one will be created with tmp_dir() +        # the file is not created or modified. +        if dir is None: +            dir = self.tmp_dir() +        return os.path.normpath(os.path.abspath(os.path.join(dir, path))) + +  class ResourceUsingTestCase(TestCase):      def setUp(self):          super(ResourceUsingTestCase, self).setUp() @@ -216,37 +235,6 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):          return root -def import_httpretty(): -    """Import HTTPretty and monkey patch Python 3.4 issue. -    See https://github.com/gabrielfalcao/HTTPretty/pull/193 and -    as well as https://github.com/gabrielfalcao/HTTPretty/issues/221. - -    Lifted from -    https://github.com/inveniosoftware/datacite/blob/master/tests/helpers.py -    """ -    if not FIX_HTTPRETTY: -        import httpretty -    else: -        import socket -        old_SocketType = socket.SocketType - -        import httpretty -        from httpretty import core - -        def sockettype_patch(f): -            @functools.wraps(f) -            def inner(*args, **kwargs): -                f(*args, **kwargs) -                socket.SocketType = old_SocketType -                socket.__dict__['SocketType'] = old_SocketType -            return inner - -        core.httpretty.disable = sockettype_patch( -            httpretty.httpretty.disable -        ) -    return httpretty - -  class HttprettyTestCase(TestCase):      # necessary as http_proxy gets in the way of httpretty      # https://github.com/gabrielfalcao/HTTPretty/issues/122 @@ -262,23 +250,10 @@ class HttprettyTestCase(TestCase):          super(HttprettyTestCase, self).tearDown() -class TempDirTestCase(TestCase): -    # provide a tempdir per class, not per test. -    def setUp(self): -        super(TempDirTestCase, self).setUp() -        self.tmp = tempfile.mkdtemp() -        self.addCleanup(shutil.rmtree, self.tmp) - -    def tmp_path(self, path): -        if path.startswith(os.path.sep): -            path = "." + path - -        return os.path.normpath(os.path.join(self.tmp, path)) - -  def populate_dir(path, files):      if not os.path.exists(path):          os.makedirs(path) +    ret = []      for (name, content) in files.items():          p = os.path.join(path, name)          util.ensure_dir(os.path.dirname(p)) @@ -288,6 +263,9 @@ def populate_dir(path, files):              else:                  fp.write(content.encode('utf-8'))              fp.close() +        ret.append(p) + +    return ret  def dir2dict(startdir, prefix=None): diff --git a/tests/unittests/test__init__.py b/tests/unittests/test__init__.py index 7b6f8c4e..781f6d54 100644 --- a/tests/unittests/test__init__.py +++ b/tests/unittests/test__init__.py @@ -1,16 +1,18 @@  # This file is part of cloud-init. See LICENSE file for license information. +import logging  import os  import shutil  import tempfile +from cloudinit.cmd import main  from cloudinit import handlers  from cloudinit import helpers  from cloudinit import settings  from cloudinit import url_helper  from cloudinit import util -from .helpers import TestCase, ExitStack, mock +from .helpers import TestCase, CiTestCase, ExitStack, mock  class FakeModule(handlers.Handler): @@ -170,44 +172,68 @@ class TestHandlerHandlePart(TestCase):              self.data, self.ctype, self.filename, self.payload) -class TestCmdlineUrl(TestCase): -    def test_invalid_content(self): -        url = "http://example.com/foo" -        key = "mykey" -        payload = b"0" -        cmdline = "ro %s=%s bar=1" % (key, url) +class TestCmdlineUrl(CiTestCase): +    def test_parse_cmdline_url_nokey_raises_keyerror(self): +        self.assertRaises( +            KeyError, main.parse_cmdline_url, 'root=foo bar single') -        with mock.patch('cloudinit.url_helper.readurl', -                        return_value=url_helper.StringResponse(payload)): -            self.assertEqual( -                util.get_cmdline_url(names=[key], starts="xxxxxx", -                                     cmdline=cmdline), -                (key, url, None)) +    def test_parse_cmdline_url_found(self): +        cmdline = 'root=foo bar single url=http://example.com arg1 -v' +        self.assertEqual( +            ('url', 'http://example.com'), main.parse_cmdline_url(cmdline)) -    def test_valid_content(self): -        url = "http://example.com/foo" -        key = "mykey" -        payload = b"xcloud-config\nmydata: foo\nbar: wark\n" +    @mock.patch('cloudinit.cmd.main.util.read_file_or_url') +    def test_invalid_content(self, m_read): +        key = "cloud-config-url" +        url = 'http://example.com/foo'          cmdline = "ro %s=%s bar=1" % (key, url) +        m_read.return_value = url_helper.StringResponse(b"unexpected blob") -        with mock.patch('cloudinit.url_helper.readurl', -                        return_value=url_helper.StringResponse(payload)): -            self.assertEqual( -                util.get_cmdline_url(names=[key], starts=b"xcloud-config", -                                     cmdline=cmdline), -                (key, url, payload)) +        fpath = self.tmp_path("ccfile") +        lvl, msg = main.attempt_cmdline_url( +            fpath, network=True, cmdline=cmdline) +        self.assertEqual(logging.WARN, lvl) +        self.assertIn(url, msg) +        self.assertFalse(os.path.exists(fpath)) -    def test_no_key_found(self): +    @mock.patch('cloudinit.cmd.main.util.read_file_or_url') +    def test_valid_content(self, m_read):          url = "http://example.com/foo" -        key = "mykey" -        cmdline = "ro %s=%s bar=1" % (key, url) - -        with mock.patch('cloudinit.url_helper.readurl', -                        return_value=url_helper.StringResponse(b'')): -            self.assertEqual( -                util.get_cmdline_url(names=["does-not-appear"], -                                     starts="#cloud-config", cmdline=cmdline), -                (None, None, None)) +        payload = b"#cloud-config\nmydata: foo\nbar: wark\n" +        cmdline = "ro %s=%s bar=1" % ('cloud-config-url', url) + +        m_read.return_value = url_helper.StringResponse(payload) +        fpath = self.tmp_path("ccfile") +        lvl, msg = main.attempt_cmdline_url( +            fpath, network=True, cmdline=cmdline) +        self.assertEqual(util.load_file(fpath, decode=False), payload) +        self.assertEqual(logging.INFO, lvl) +        self.assertIn(url, msg) + +    @mock.patch('cloudinit.cmd.main.util.read_file_or_url') +    def test_no_key_found(self, m_read): +        cmdline = "ro mykey=http://example.com/foo root=foo" +        fpath = self.tmp_path("ccpath") +        lvl, msg = main.attempt_cmdline_url( +            fpath, network=True, cmdline=cmdline) + +        m_read.assert_not_called() +        self.assertFalse(os.path.exists(fpath)) +        self.assertEqual(logging.DEBUG, lvl) + +    @mock.patch('cloudinit.cmd.main.util.read_file_or_url') +    def test_exception_warns(self, m_read): +        url = "http://example.com/foo" +        cmdline = "ro cloud-config-url=%s root=LABEL=bar" % url +        fpath = self.tmp_path("ccfile") +        m_read.side_effect = url_helper.UrlError( +            cause="Unexpected Error", url="http://example.com/foo") + +        lvl, msg = main.attempt_cmdline_url( +            fpath, network=True, cmdline=cmdline) +        self.assertEqual(logging.WARN, lvl) +        self.assertIn(url, msg) +        self.assertFalse(os.path.exists(fpath))  # vi: ts=4 expandtab diff --git a/tests/unittests/test_atomic_helper.py b/tests/unittests/test_atomic_helper.py index e170c7c3..515919d8 100644 --- a/tests/unittests/test_atomic_helper.py +++ b/tests/unittests/test_atomic_helper.py @@ -6,10 +6,10 @@ import stat  from cloudinit import atomic_helper -from . import helpers +from .helpers import CiTestCase -class TestAtomicHelper(helpers.TempDirTestCase): +class TestAtomicHelper(CiTestCase):      def test_basic_usage(self):          """write_file takes bytes if no omode."""          path = self.tmp_path("test_basic_usage") diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py index 4092d9ca..4ad86bb6 100644 --- a/tests/unittests/test_data.py +++ b/tests/unittests/test_data.py @@ -564,12 +564,12 @@ class TestConvertString(helpers.TestCase):  class TestFetchBaseConfig(helpers.TestCase): - -    def test_only_builtin_gets_builtin2(self): +    def test_only_builtin_gets_builtin(self):          ret = helpers.wrap_and_call( -            'cloudinit.stages.util', -            {'read_conf_with_confd': None, -             'read_conf_from_cmdline': None}, +            'cloudinit.stages', +            {'util.read_conf_with_confd': None, +             'util.read_conf_from_cmdline': None, +             'read_runtime_config': {'return_value': {}}},              stages.fetch_base_config)          self.assertEqual(util.get_builtin_cfg(), ret) @@ -578,9 +578,11 @@ class TestFetchBaseConfig(helpers.TestCase):          test_key = sorted(builtin)[0]          test_value = 'test'          ret = helpers.wrap_and_call( -            'cloudinit.stages.util', -            {'read_conf_with_confd': {'return_value': {test_key: test_value}}, -             'read_conf_from_cmdline': None}, +            'cloudinit.stages', +            {'util.read_conf_with_confd': +                {'return_value': {test_key: test_value}}, +             'util.read_conf_from_cmdline': None, +             'read_runtime_config': {'return_value': {}}},              stages.fetch_base_config)          self.assertEqual(ret.get(test_key), test_value)          builtin[test_key] = test_value @@ -592,25 +594,44 @@ class TestFetchBaseConfig(helpers.TestCase):          test_value = 'test'          cmdline = {test_key: test_value}          ret = helpers.wrap_and_call( -            'cloudinit.stages.util', -            {'read_conf_from_cmdline': {'return_value': cmdline}, -             'read_conf_with_confd': None}, +            'cloudinit.stages', +            {'util.read_conf_from_cmdline': {'return_value': cmdline}, +             'util.read_conf_with_confd': None, +             'read_runtime_config': None},              stages.fetch_base_config)          self.assertEqual(ret.get(test_key), test_value)          builtin[test_key] = test_value          self.assertEqual(ret, builtin) -    def test_cmdline_overrides_conf_d_and_defaults(self): +    def test_cmdline_overrides_confd_runtime_and_defaults(self):          builtin = {'key1': 'value0', 'key3': 'other2'}          conf_d = {'key1': 'value1', 'key2': 'other1'}          cmdline = {'key3': 'other3', 'key2': 'other2'} +        runtime = {'key3': 'runtime3'}          ret = helpers.wrap_and_call( -            'cloudinit.stages.util', -            {'read_conf_with_confd': {'return_value': conf_d}, -             'get_builtin_cfg': {'return_value': builtin}, -             'read_conf_from_cmdline': {'return_value': cmdline}}, +            'cloudinit.stages', +            {'util.read_conf_with_confd': {'return_value': conf_d}, +             'util.get_builtin_cfg': {'return_value': builtin}, +             'read_runtime_config': {'return_value': runtime}, +             'util.read_conf_from_cmdline': {'return_value': cmdline}},              stages.fetch_base_config)          self.assertEqual(ret, {'key1': 'value1', 'key2': 'other2',                                 'key3': 'other3'}) +    def test_order_precedence_is_builtin_system_runtime_cmdline(self): +        builtin = {'key1': 'builtin0', 'key3': 'builtin3'} +        conf_d = {'key1': 'confd1', 'key2': 'confd2', 'keyconfd1': 'kconfd1'} +        runtime = {'key1': 'runtime1', 'key2': 'runtime2'} +        cmdline = {'key1': 'cmdline1'} +        ret = helpers.wrap_and_call( +            'cloudinit.stages', +            {'util.read_conf_with_confd': {'return_value': conf_d}, +             'util.get_builtin_cfg': {'return_value': builtin}, +             'util.read_conf_from_cmdline': {'return_value': cmdline}, +             'read_runtime_config': {'return_value': runtime}, +             }, +            stages.fetch_base_config) +        self.assertEqual(ret, {'key1': 'cmdline1', 'key2': 'runtime2', +                               'key3': 'builtin3', 'keyconfd1': 'kconfd1'}) +  # vi: ts=4 expandtab diff --git a/tests/unittests/test_datasource/test_gce.py b/tests/unittests/test_datasource/test_gce.py index 4f667678..4f83454e 100644 --- a/tests/unittests/test_datasource/test_gce.py +++ b/tests/unittests/test_datasource/test_gce.py @@ -4,6 +4,7 @@  #  # This file is part of cloud-init. See LICENSE file for license information. +import httpretty  import re  from base64 import b64encode, b64decode @@ -15,7 +16,6 @@ from cloudinit.sources import DataSourceGCE  from .. import helpers as test_helpers -httpretty = test_helpers.import_httpretty()  GCE_META = {      'instance/id': '123', @@ -59,6 +59,8 @@ def _set_mock_metadata(gce_meta=None):          else:              return (404, headers, '') +    # reset is needed. https://github.com/gabrielfalcao/HTTPretty/issues/316 +    httpretty.reset()      httpretty.register_uri(httpretty.GET, MD_URL_RE, body=_request_callback) diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py index e5b6fcc6..7bf55084 100644 --- a/tests/unittests/test_datasource/test_openstack.py +++ b/tests/unittests/test_datasource/test_openstack.py @@ -5,6 +5,7 @@  # This file is part of cloud-init. See LICENSE file for license information.  import copy +import httpretty as hp  import json  import re @@ -20,8 +21,6 @@ from cloudinit.sources import DataSourceOpenStack as ds  from cloudinit.sources.helpers import openstack  from cloudinit import util -hp = test_helpers.import_httpretty() -  BASE_URL = "http://169.254.169.254"  PUBKEY = u'ssh-rsa AAAAB3NzaC1....sIkJhq8wdX+4I3A4cYbYP ubuntu@server-460\n'  EC2_META = { @@ -232,7 +231,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):                                         None,                                         helpers.Paths({}))          self.assertIsNone(ds_os.version) -        found = ds_os.get_data(timeout=0.1, retries=0) +        found = ds_os.get_data()          self.assertTrue(found)          self.assertEqual(2, ds_os.version)          md = dict(ds_os.metadata) @@ -256,7 +255,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):                                         None,                                         helpers.Paths({}))          self.assertIsNone(ds_os.version) -        found = ds_os.get_data(timeout=0.1, retries=0) +        found = ds_os.get_data()          self.assertFalse(found)          self.assertIsNone(ds_os.version) @@ -275,7 +274,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):              'timeout': 0,          }          self.assertIsNone(ds_os.version) -        found = ds_os.get_data(timeout=0.1, retries=0) +        found = ds_os.get_data()          self.assertFalse(found)          self.assertIsNone(ds_os.version) @@ -298,7 +297,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):              'timeout': 0,          }          self.assertIsNone(ds_os.version) -        found = ds_os.get_data(timeout=0.1, retries=0) +        found = ds_os.get_data()          self.assertFalse(found)          self.assertIsNone(ds_os.version) diff --git a/tests/unittests/test_distros/test_user_data_normalize.py b/tests/unittests/test_distros/test_user_data_normalize.py index 88746e0a..88746e0a 100755..100644 --- a/tests/unittests/test_distros/test_user_data_normalize.py +++ b/tests/unittests/test_distros/test_user_data_normalize.py diff --git a/tests/unittests/test_ec2_util.py b/tests/unittests/test_ec2_util.py index 4a33d747..65fdb519 100644 --- a/tests/unittests/test_ec2_util.py +++ b/tests/unittests/test_ec2_util.py @@ -1,12 +1,12 @@  # This file is part of cloud-init. See LICENSE file for license information. +import httpretty as hp +  from . import helpers  from cloudinit import ec2_utils as eu  from cloudinit import url_helper as uh -hp = helpers.import_httpretty() -  class TestEc2Util(helpers.HttprettyTestCase):      VERSION = 'latest' @@ -140,4 +140,49 @@ class TestEc2Util(helpers.HttprettyTestCase):          self.assertEqual(bdm['ami'], 'sdb')          self.assertEqual(bdm['ephemeral0'], 'sdc') +    @hp.activate +    def test_metadata_no_security_credentials(self): +        base_url = 'http://169.254.169.254/%s/meta-data/' % (self.VERSION) +        hp.register_uri(hp.GET, base_url, status=200, +                        body="\n".join(['instance-id', +                                        'iam/'])) +        hp.register_uri(hp.GET, uh.combine_url(base_url, 'instance-id'), +                        status=200, body='i-0123451689abcdef0') +        hp.register_uri(hp.GET, +                        uh.combine_url(base_url, 'iam/'), +                        status=200, +                        body="\n".join(['info/', 'security-credentials/'])) +        hp.register_uri(hp.GET, +                        uh.combine_url(base_url, 'iam/info/'), +                        status=200, +                        body='LastUpdated') +        hp.register_uri(hp.GET, +                        uh.combine_url(base_url, 'iam/info/LastUpdated'), +                        status=200, body='2016-10-27T17:29:39Z') +        hp.register_uri(hp.GET, +                        uh.combine_url(base_url, 'iam/security-credentials/'), +                        status=200, +                        body='ReadOnly/') +        hp.register_uri(hp.GET, +                        uh.combine_url(base_url, +                                       'iam/security-credentials/ReadOnly/'), +                        status=200, +                        body="\n".join(['LastUpdated', 'Expiration'])) +        hp.register_uri(hp.GET, +                        uh.combine_url( +                            base_url, +                            'iam/security-credentials/ReadOnly/LastUpdated'), +                        status=200, body='2016-10-27T17:28:17Z') +        hp.register_uri(hp.GET, +                        uh.combine_url( +                            base_url, +                            'iam/security-credentials/ReadOnly/Expiration'), +                        status=200, body='2016-10-28T00:00:34Z') +        md = eu.get_instance_metadata(self.VERSION, retries=0, timeout=0.1) +        self.assertEqual(md['instance-id'], 'i-0123451689abcdef0') +        iam = md['iam'] +        self.assertEqual(1, len(iam)) +        self.assertEqual(iam['info']['LastUpdated'], '2016-10-27T17:29:39Z') +        self.assertNotIn('security-credentials', iam) +  # vi: ts=4 expandtab diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 1090282a..4b03ff72 100755..100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -8,11 +8,10 @@ from cloudinit.net import sysconfig  from cloudinit.sources.helpers import openstack  from cloudinit import util +from .helpers import CiTestCase  from .helpers import dir2dict  from .helpers import mock  from .helpers import populate_dir -from .helpers import TempDirTestCase -from .helpers import TestCase  import base64  import copy @@ -20,8 +19,6 @@ import gzip  import io  import json  import os -import shutil -import tempfile  import textwrap  import yaml @@ -166,6 +163,91 @@ nameserver 172.19.0.12              ('etc/udev/rules.d/70-persistent-net.rules',               "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',                        'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] +    }, +    { +        'in_data': { +            "services": [{"type": "dns", "address": "172.19.0.12"}], +            "networks": [{ +                "network_id": "public-ipv4", +                "type": "ipv4", "netmask": "255.255.252.0", +                "link": "tap1a81968a-79", +                "routes": [{ +                    "netmask": "0.0.0.0", +                    "network": "0.0.0.0", +                    "gateway": "172.19.3.254", +                }], +                "ip_address": "172.19.1.34", "id": "network0" +            }, { +                "network_id": "private-ipv4", +                "type": "ipv4", "netmask": "255.255.255.0", +                "link": "tap1a81968a-79", +                "routes": [], +                "ip_address": "10.0.0.10", "id": "network1" +            }], +            "links": [ +                { +                    "ethernet_mac_address": "fa:16:3e:ed:9a:59", +                    "mtu": None, "type": "bridge", "id": +                    "tap1a81968a-79", +                    "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" +                }, +            ], +        }, +        'in_macs': { +            'fa:16:3e:ed:9a:59': 'eth0', +        }, +        'out_sysconfig': [ +            ('etc/sysconfig/network-scripts/ifcfg-eth0', +             """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=none +DEVICE=eth0 +HWADDR=fa:16:3e:ed:9a:59 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip()), +            ('etc/sysconfig/network-scripts/ifcfg-eth0:0', +             """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=static +DEFROUTE=yes +DEVICE=eth0:0 +GATEWAY=172.19.3.254 +HWADDR=fa:16:3e:ed:9a:59 +IPADDR=172.19.1.34 +NETMASK=255.255.252.0 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip()), +            ('etc/sysconfig/network-scripts/ifcfg-eth0:1', +             """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=static +DEVICE=eth0:1 +HWADDR=fa:16:3e:ed:9a:59 +IPADDR=10.0.0.10 +NETMASK=255.255.255.0 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip()), +            ('etc/resolv.conf', +             """ +; Created by cloud-init on instance boot automatically, do not edit. +; +nameserver 172.19.0.12 +""".lstrip()), +            ('etc/udev/rules.d/70-persistent-net.rules', +             "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', +                      'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]      }  ] @@ -222,11 +304,9 @@ NETWORK_CONFIGS = {              auto eth99              iface eth99 inet dhcp -                post-up ifup eth99:1 - -            auto eth99:1 -            iface eth99:1 inet static +            # control-alias eth99 +            iface eth99 inet static                  address 192.168.21.3/24                  dns-nameservers 8.8.8.8 8.8.4.4                  dns-search barley.maas sach.maas @@ -264,6 +344,27 @@ NETWORK_CONFIGS = {                      - wark.maas          """),      }, +    'v4_and_v6': { +        'expected_eni': textwrap.dedent("""\ +            auto lo +            iface lo inet loopback + +            auto iface0 +            iface iface0 inet dhcp + +            # control-alias iface0 +            iface iface0 inet6 dhcp +        """).rstrip(' '), +        'yaml': textwrap.dedent("""\ +            version: 1 +            config: +              - type: 'physical' +                name: 'iface0' +                subnets: +                - {'type': 'dhcp4'} +                - {'type': 'dhcp6'} +        """).rstrip(' '), +    },      'all': {          'expected_eni': ("""\  auto lo @@ -301,11 +402,9 @@ iface br0 inet static      address 192.168.14.2/24      bridge_ports eth3 eth4      bridge_stp off -    post-up ifup br0:1 - -auto br0:1 -iface br0:1 inet6 static +# control-alias br0 +iface br0 inet6 static      address 2001:1::1/64  auto bond0.200 @@ -322,11 +421,9 @@ iface eth0.101 inet static      mtu 1500      vlan-raw-device eth0      vlan_id 101 -    post-up ifup eth0.101:1 - -auto eth0.101:1 -iface eth0.101:1 inet static +# control-alias eth0.101 +iface eth0.101 inet static      address 192.168.2.10/24  post-up route add -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true @@ -478,7 +575,7 @@ def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,      mock_sys_dev_path.side_effect = sys_dev_path -class TestSysConfigRendering(TestCase): +class TestSysConfigRendering(CiTestCase):      @mock.patch("cloudinit.net.sys_dev_path")      @mock.patch("cloudinit.net.read_sys_net") @@ -486,8 +583,7 @@ class TestSysConfigRendering(TestCase):      def test_default_generation(self, mock_get_devicelist,                                  mock_read_sys_net,                                  mock_sys_dev_path): -        tmp_dir = tempfile.mkdtemp() -        self.addCleanup(shutil.rmtree, tmp_dir) +        tmp_dir = self.tmp_dir()          _setup_test(tmp_dir, mock_get_devicelist,                      mock_read_sys_net, mock_sys_dev_path) @@ -518,10 +614,8 @@ USERCTL=no              self.assertEqual(expected_content, content)      def test_openstack_rendering_samples(self): -        tmp_dir = tempfile.mkdtemp() -        self.addCleanup(shutil.rmtree, tmp_dir) -        render_dir = os.path.join(tmp_dir, "render")          for os_sample in OS_SAMPLES: +            render_dir = self.tmp_dir()              ex_input = os_sample['in_data']              ex_mac_addrs = os_sample['in_macs']              network_cfg = openstack.convert_net_json( @@ -535,7 +629,7 @@ USERCTL=no                      self.assertEqual(expected_content, fh.read()) -class TestEniNetRendering(TestCase): +class TestEniNetRendering(CiTestCase):      @mock.patch("cloudinit.net.sys_dev_path")      @mock.patch("cloudinit.net.read_sys_net") @@ -543,8 +637,7 @@ class TestEniNetRendering(TestCase):      def test_default_generation(self, mock_get_devicelist,                                  mock_read_sys_net,                                  mock_sys_dev_path): -        tmp_dir = tempfile.mkdtemp() -        self.addCleanup(shutil.rmtree, tmp_dir) +        tmp_dir = self.tmp_dir()          _setup_test(tmp_dir, mock_get_devicelist,                      mock_read_sys_net, mock_sys_dev_path) @@ -576,7 +669,7 @@ iface eth1000 inet dhcp          self.assertEqual(expected.lstrip(), contents.lstrip()) -class TestEniNetworkStateToEni(TestCase): +class TestEniNetworkStateToEni(CiTestCase):      mycfg = {          'config': [{"type": "physical", "name": "eth0",                      "mac_address": "c0:d6:9f:2c:e8:80", @@ -607,7 +700,7 @@ class TestEniNetworkStateToEni(TestCase):          self.assertNotIn("hwaddress", rendered) -class TestCmdlineConfigParsing(TestCase): +class TestCmdlineConfigParsing(CiTestCase):      simple_cfg = {          'config': [{"type": "physical", "name": "eth0",                      "mac_address": "c0:d6:9f:2c:e8:80", @@ -665,7 +758,7 @@ class TestCmdlineConfigParsing(TestCase):          self.assertEqual(found, self.simple_cfg) -class TestCmdlineReadKernelConfig(TempDirTestCase): +class TestCmdlineReadKernelConfig(CiTestCase):      macs = {          'eth0': '14:02:ec:42:48:00',          'eno1': '14:02:ec:42:48:01', @@ -673,8 +766,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase):      def test_ip_cmdline_read_kernel_cmdline_ip(self):          content = {'net-eth0.conf': DHCP_CONTENT_1} -        populate_dir(self.tmp, content) -        files = [os.path.join(self.tmp, k) for k in content.keys()] +        files = sorted(populate_dir(self.tmp_dir(), content))          found = cmdline.read_kernel_cmdline_config(              files=files, cmdline='foo ip=dhcp', mac_addrs=self.macs)          exp1 = copy.deepcopy(DHCP_EXPECTED_1) @@ -684,8 +776,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase):      def test_ip_cmdline_read_kernel_cmdline_ip6(self):          content = {'net6-eno1.conf': DHCP6_CONTENT_1} -        populate_dir(self.tmp, content) -        files = [os.path.join(self.tmp, k) for k in content.keys()] +        files = sorted(populate_dir(self.tmp_dir(), content))          found = cmdline.read_kernel_cmdline_config(              files=files, cmdline='foo ip6=dhcp root=/dev/sda',              mac_addrs=self.macs) @@ -701,8 +792,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase):      def test_ip_cmdline_read_kernel_cmdline_none(self):          # if there is no ip= or ip6= on cmdline, return value should be None          content = {'net6-eno1.conf': DHCP6_CONTENT_1} -        populate_dir(self.tmp, content) -        files = [os.path.join(self.tmp, k) for k in content.keys()] +        files = sorted(populate_dir(self.tmp_dir(), content))          found = cmdline.read_kernel_cmdline_config(              files=files, cmdline='foo root=/dev/sda', mac_addrs=self.macs)          self.assertEqual(found, None) @@ -710,8 +800,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase):      def test_ip_cmdline_both_ip_ip6(self):          content = {'net-eth0.conf': DHCP_CONTENT_1,                     'net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')} -        populate_dir(self.tmp, content) -        files = [os.path.join(self.tmp, k) for k in sorted(content.keys())] +        files = sorted(populate_dir(self.tmp_dir(), content))          found = cmdline.read_kernel_cmdline_config(              files=files, cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs) @@ -725,14 +814,12 @@ class TestCmdlineReadKernelConfig(TempDirTestCase):          self.assertEqual(found['config'], expected) -class TestEniRoundTrip(TestCase): -    def setUp(self): -        super(TestCase, self).setUp() -        self.tmp_dir = tempfile.mkdtemp() -        self.addCleanup(shutil.rmtree, self.tmp_dir) - +class TestEniRoundTrip(CiTestCase):      def _render_and_read(self, network_config=None, state=None, eni_path=None, -                         links_prefix=None, netrules_path=None): +                         links_prefix=None, netrules_path=None, dir=None): +        if dir is None: +            dir = self.tmp_dir() +          if network_config:              ns = network_state.parse_net_config_data(network_config)          elif state: @@ -747,8 +834,8 @@ class TestEniRoundTrip(TestCase):              config={'eni_path': eni_path, 'links_path_prefix': links_prefix,                      'netrules_path': netrules_path}) -        renderer.render_network_state(self.tmp_dir, ns) -        return dir2dict(self.tmp_dir) +        renderer.render_network_state(dir, ns) +        return dir2dict(dir)      def testsimple_convert_and_render(self):          network_config = eni.convert_eni_data(EXAMPLE_ENI) @@ -771,6 +858,13 @@ class TestEniRoundTrip(TestCase):              entry['expected_eni'].splitlines(),              files['/etc/network/interfaces'].splitlines()) +    def testsimple_render_v4_and_v6(self): +        entry = NETWORK_CONFIGS['v4_and_v6'] +        files = self._render_and_read(network_config=yaml.load(entry['yaml'])) +        self.assertEqual( +            entry['expected_eni'].splitlines(), +            files['/etc/network/interfaces'].splitlines()) +      def test_routes_rendered(self):          # as reported in bug 1649652          conf = [ diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py index 55971b5e..991f45a6 100644 --- a/tests/unittests/test_sshutil.py +++ b/tests/unittests/test_sshutil.py @@ -32,6 +32,22 @@ VALID_CONTENT = {          "YWpMfYdPUnE7u536WqzFmsaqJctz3gBxH9Ex7dFtrxR4qiqEr9Qtlu3xGn7Bw07"          "/+i1D+ey3ONkZLN+LQ714cgj8fRS4Hj29SCmXp5Kt5/82cD/VN3NtHw=="      ), +    'ecdsa-sha2-nistp256': ( +        "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMy/WuXq5MF" +        "r5hVQ9EEKKUTF7vUaOkgxUh6bNsCs9SFMVslIm1zM/WJYwUv52LdEePjtDYiV4A" +        "l2XthJ9/bs7Pc=" +    ), +    'ecdsa-sha2-nistp521': ( +        "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABOdNTkh9F" +        "McK4hZRLs5LTXBEXwNr0+Yg9uvJYRFcz2ZlnjYX9tM4Z3QQFjqogU4pU+zpKLqZ" +        "5VE4Jcnb1T608UywBIdXkSFZT8trGJqBv9nFWGgmTX3KP8kiBbihpuv1cGwglPl" +        "Hxs50A42iP0JiT7auGtEAGsu/uMql323GTGb4171Q==" +    ), +    'ecdsa-sha2-nistp384': ( +        "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBAnoqFU9Gnl" +        "LcsEuCJnobs/c6whzvjCgouaOO61kgXNtIxyF4Wkutg6xaGYgBBt/phb7a2TurI" +        "bcIBuzJ/mP22UyUAbNnBfStAEBmYbrTf1EfiMCYUAr1XnL0UdYmZ8HFg==" +    ),  }  TEST_OPTIONS = ( @@ -44,7 +60,13 @@ class TestAuthKeyLineParser(test_helpers.TestCase):      def test_simple_parse(self):          # test key line with common 3 fields (keytype, base64, comment)          parser = ssh_util.AuthKeyLineParser() -        for ktype in ['rsa', 'ecdsa', 'dsa']: +        ecdsa_types = [ +            'ecdsa-sha2-nistp256', +            'ecdsa-sha2-nistp384', +            'ecdsa-sha2-nistp521', +        ] + +        for ktype in ['rsa', 'ecdsa', 'dsa'] + ecdsa_types:              content = VALID_CONTENT[ktype]              comment = 'user-%s@host' % ktype              line = ' '.join((ktype, content, comment,)) | 
