From a2249942522d140a063acdc007aced991d4f0588 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 5 May 2016 14:57:48 -0700 Subject: Work on refactoring (and adding) network conversion tests --- .../unittests/test_datasource/test_configdrive.py | 104 +++++++++++++++++---- tests/unittests/test_net.py | 1 + 2 files changed, 88 insertions(+), 17 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index 89b15f54..06fba202 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -355,6 +355,14 @@ class TestConfigDriveDataSource(TestCase): self.assertEqual(myds.get_public_ssh_keys(), [OSTACK_META['public_keys']['mykey']]) + +class TestNetJson(TestCase): + def setUp(self): + super(TestNetJson, self).setUp() + self.tmp = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.tmp) + self.maxDiff = None + def test_network_data_is_found(self): """Verify that network_data is present in ds in config-drive-v2.""" populate_dir(self.tmp, CFG_DRIVE_FILES_V2) @@ -365,31 +373,94 @@ class TestConfigDriveDataSource(TestCase): """Verify that network_data is converted and present on ds object.""" populate_dir(self.tmp, CFG_DRIVE_FILES_V2) myds = cfg_ds_from_dir(self.tmp) - network_config = ds.convert_network_data(NETWORK_DATA) + network_config = openstack.convert_net_json(NETWORK_DATA) self.assertEqual(myds.network_config, network_config) + def test_network_config_conversions(self): + """Tests a bunch of input network json and checks the expected conversions.""" + in_datas = [ + NETWORK_DATA, + { + 'services': [{'type': 'dns', 'address': '172.19.0.12'}], + 'networks': [ + {'network_id': 'dacd568d-5be6-4786-91fe-750c374b78b4', + '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', + }], + 'links': [ + {'type': 'bridge', + 'vif_id': '1a81968a-797a-400f-8a80-567f997eb93f', + 'ethernet_mac_address': 'fa:16:3e:ed:9a:59', + 'id': 'tap1a81968a-79', 'mtu': None}] + }, + ] + out_datas = [ + { + 'version': 1, + 'config': [ + { + 'subnets': [{'type': 'dhcp4'}], + 'type': 'physical', + 'mac_address': 'fa:16:3e:69:b0:58', + 'name': 'tap2ecc7709-b3', + }, + { + 'subnets': [{'type': 'dhcp4'}], + 'type': 'physical', + 'mac_address': 'fa:16:3e:d4:57:ad', + 'name': 'tap2f88d109-5b', + }, + { + 'subnets': [{'type': 'dhcp4'}], + 'type': 'physical', + 'mac_address': 'fa:16:3e:05:30:fe', + 'name': 'tap1a5382f8-04', + }, + { + 'type': 'nameserver', + 'address': '199.204.44.24', + }, + { + 'type': 'nameserver', + 'address': '199.204.47.54', + } + ], + + }, + { + 'version': 1, + 'config': [ + { + 'name': 'tap1a81968a-79', + + } + ], + }, + ] + for in_data, out_data in zip(in_datas, out_datas): + self.assertEqual(openstack.convert_net_json(in_data), + out_data) + def cfg_ds_from_dir(seed_d): - found = ds.read_config_drive(seed_d) cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN, None, helpers.Paths({})) - populate_ds_from_read_config(cfg_ds, seed_d, found) + cfg_ds.seed_dir = seed_d + if not cfg_ds.get_data(skip_first_boot=True): + raise RuntimeError("Data source did not extract itself from" + " seed directory %s" % seed_d) return cfg_ds -def populate_ds_from_read_config(cfg_ds, source, results): - """Patch the DataSourceConfigDrive from the results of - read_config_drive_dir hopefully in line with what it would have - if cfg_ds.get_data had been successfully called""" - cfg_ds.source = source - cfg_ds.metadata = results.get('metadata') - cfg_ds.ec2_metadata = results.get('ec2-metadata') - cfg_ds.userdata_raw = results.get('userdata') - cfg_ds.version = results.get('version') - cfg_ds.network_json = results.get('networkdata') - cfg_ds._network_config = ds.convert_network_data(cfg_ds.network_json) - - def populate_dir(seed_dir, files): for (name, content) in files.items(): path = os.path.join(seed_dir, name) @@ -400,7 +471,6 @@ def populate_dir(seed_dir, files): mode = "w" else: mode = "wb" - with open(path, mode) as fp: fp.write(content) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 09235c4d..a0cdc493 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -121,6 +121,7 @@ class TestNetConfigParsing(TestCase): self.assertEqual(found, self.simple_cfg) + def _gzip_data(data): with io.BytesIO() as iobuf: gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf) -- cgit v1.2.3 From f5a1662e4a2cbbdee0766276fd516a25306545d6 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 5 May 2016 16:38:36 -0700 Subject: Get this test of net json -> net config working --- tests/unittests/test_datasource/test_configdrive.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index 06fba202..8beaf95e 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -441,7 +441,25 @@ class TestNetJson(TestCase): 'config': [ { 'name': 'tap1a81968a-79', - + 'mac_address': 'fa:16:3e:ed:9a:59', + 'mtu': None, + 'type': 'bridge', + 'subnets': [ + { + 'address': '172.19.1.34', + 'netmask': '255.255.252.0', + 'type': 'static', + 'routes': [{ + 'gateway': '172.19.3.254', + 'netmask': '0.0.0.0', + 'network': '0.0.0.0', + }], + } + ] + }, + { + 'type': 'nameserver', + 'address': '172.19.0.12', } ], }, -- cgit v1.2.3 From eeabe2ed7480dc653c9d65d218738a8ed5a21579 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 10 May 2016 14:42:19 +0200 Subject: basic apt_source test --- .../test_handler/test_handler_apt_source.py | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/unittests/test_handler/test_handler_apt_source.py (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py new file mode 100644 index 00000000..b45fc1d1 --- /dev/null +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -0,0 +1,87 @@ +""" test_handler_apt_source +Testing various config variations of the apt_source config +""" +import os +import shutil +import tempfile +import re + +from cloudinit import distros +from cloudinit import util +from cloudinit.config import cc_apt_configure + +from ..helpers import TestCase + +UNKNOWN_ARCH_INFO = { + 'arches': ['default'], + 'failsafe': {'primary': 'http://fs-primary-default', + 'security': 'http://fs-security-default'} +} + +PACKAGE_MIRRORS = [ + {'arches': ['i386', 'amd64'], + 'failsafe': {'primary': 'http://fs-primary-intel', + 'security': 'http://fs-security-intel'}, + 'search': { + 'primary': ['http://%(ec2_region)s.ec2/', + 'http://%(availability_zone)s.clouds/'], + 'security': ['http://security-mirror1-intel', + 'http://security-mirror2-intel']}}, + {'arches': ['armhf', 'armel'], + 'failsafe': {'primary': 'http://fs-primary-arm', + 'security': 'http://fs-security-arm'}}, + UNKNOWN_ARCH_INFO +] + +GAPMI = distros._get_arch_package_mirror_info + +def load_tfile_or_url(*args, **kwargs): + """ load_tfile_or_url + load file and return content after decoding + """ + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) + +class TestAptSourceConfig(TestCase): + """ TestAptSourceConfig + Main Class to test apt_source configs + """ + def setUp(self): + super(TestAptSourceConfig, self).setUp() + self.tmp = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.tmp) + self.aptlistfile = os.path.join(self.tmp, "single-deb.list") + + + @staticmethod + def _get_default_params(): + """ get_default_params + Get the most basic default mrror and release info to be used in tests + """ + params = {} + params['RELEASE'] = cc_apt_configure.get_release() + params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" + return params + + @staticmethod + def _search_apt_source(contents, params, pre, post): + return re.search(r"%s %s %s %s\n" % + (pre, params['MIRROR'], params['RELEASE'], post), + contents, flags=re.IGNORECASE) + + def test_apt_source_release(self): + """ test_apt_source_release + Test Autoreplacement of MIRROR and RELEASE in source specs + """ + params = self._get_default_params() + cfg = {'source': 'deb $MIRROR $RELEASE multiverse', + 'filename': self.aptlistfile} + + cc_apt_configure.add_sources([cfg], params) + + self.assertTrue(os.path.isfile(self.aptlistfile)) + + contents = load_tfile_or_url(self.aptlistfile) + self.assertTrue(self._search_apt_source(contents, params, + "deb", "multiverse")) + +# vi: ts=4 expandtab -- cgit v1.2.3 From b0494addabfe1d07947427ade99a00d2c7588f12 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 10 May 2016 14:59:57 +0200 Subject: split into basic and replacement test --- .../test_handler/test_handler_apt_source.py | 37 ++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index b45fc1d1..a9647156 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -62,14 +62,31 @@ class TestAptSourceConfig(TestCase): params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" return params - @staticmethod - def _search_apt_source(contents, params, pre, post): - return re.search(r"%s %s %s %s\n" % - (pre, params['MIRROR'], params['RELEASE'], post), - contents, flags=re.IGNORECASE) - def test_apt_source_release(self): - """ test_apt_source_release + def test_apt_source_basic(self): + """ test_apt_source_basic + Test Fix deb source string, has to overwrite mirror conf in params + """ + params = self._get_default_params() + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' + ' karmic-backports' + ' main universe multiverse restricted'), + 'filename': self.aptlistfile} + + cc_apt_configure.add_sources([cfg], params) + + self.assertTrue(os.path.isfile(self.aptlistfile)) + + contents = load_tfile_or_url(self.aptlistfile) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", "http://archive.ubuntu.com/ubuntu", + "karmic-backports", + "main universe multiverse restricted"), + contents, flags=re.IGNORECASE)) + + + def test_apt_source_replacement(self): + """ test_apt_source_replace Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() @@ -81,7 +98,9 @@ class TestAptSourceConfig(TestCase): self.assertTrue(os.path.isfile(self.aptlistfile)) contents = load_tfile_or_url(self.aptlistfile) - self.assertTrue(self._search_apt_source(contents, params, - "deb", "multiverse")) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", params['MIRROR'], params['RELEASE'], + "multiverse"), + contents, flags=re.IGNORECASE)) # vi: ts=4 expandtab -- cgit v1.2.3 From 8cb8502cc1c99ec787e23504cf4e9f60c01bf0fe Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 10 May 2016 15:30:49 +0200 Subject: add test_apt_source_ppa (failing for now) --- .../test_handler/test_handler_apt_source.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index a9647156..22a4accf 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -103,4 +103,25 @@ class TestAptSourceConfig(TestCase): "multiverse"), contents, flags=re.IGNORECASE)) + + def test_apt_source_ppa(self): + """ test_apt_source_ppa + Test specification of a ppa + """ + params = self._get_default_params() + cfg = {'source': 'ppa:smoser/cloud-init-test', + 'filename': self.aptlistfile} + + cc_apt_configure.add_sources([cfg], params) + + self.assertTrue(os.path.isfile(self.aptlistfile)) + + # report content before making regex + # FAIL ? goes in "untranslated" + # should become e.g. deb http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu xenial main + contents = load_tfile_or_url(self.aptlistfile) + print(contents) + self.assertTrue(1 == 2) + + # vi: ts=4 expandtab -- cgit v1.2.3 From 66f847f4cba2215490986ffede8e03299dbd5b90 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 10 May 2016 17:14:26 +0200 Subject: add test_apt_source_key for sources with a keyid to import --- .../test_handler/test_handler_apt_source.py | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 22a4accf..d9942901 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -104,6 +104,38 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) + def test_apt_source_key(self): + """ test_apt_source_key + Test specification of a source + key + """ + params = self._get_default_params() + cfg = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'keyid:': "03683F77", + 'filename': self.aptlistfile} + + cc_apt_configure.add_sources([cfg], params) + + self.assertTrue(os.path.isfile(self.aptlistfile)) + + # report content before making regex + contents = load_tfile_or_url(self.aptlistfile) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", + ('http://ppa.launchpad.net/smoser/' + 'cloud-init-test/ubuntu'), + "xenial", "main"), + contents, flags=re.IGNORECASE)) + # check if key was imported + try: + util.subp(('apt-key', 'list', '03683F77')) + except util.ProcessExecutionError as err: + print("apt-key failed. " + str(err)) + self.assertTrue(1 == 2) + + def test_apt_source_ppa(self): """ test_apt_source_ppa Test specification of a ppa -- cgit v1.2.3 From 904aeedf343af17ef88bbbaef9896d425eefa778 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 10 May 2016 20:57:30 +0200 Subject: provide valid matcher for ppa so that apt-add-repository is triggered --- .../unittests/test_handler/test_handler_apt_source.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index d9942901..10a03a8d 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -144,14 +144,21 @@ class TestAptSourceConfig(TestCase): cfg = {'source': 'ppa:smoser/cloud-init-test', 'filename': self.aptlistfile} - cc_apt_configure.add_sources([cfg], params) + # default matcher needed for ppa + matcher = re.compile(r'^[\w-]+:\w').search - self.assertTrue(os.path.isfile(self.aptlistfile)) + cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) - # report content before making regex - # FAIL ? goes in "untranslated" - # should become e.g. deb http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu xenial main - contents = load_tfile_or_url(self.aptlistfile) + # adding ppa should ignore filename (uses add-apt-repository) + self.assertFalse(os.path.isfile(self.aptlistfile)) + expected_sources_fn=('/etc/apt/sources.list.d/' + 'smoser-ubuntu-cloud-init-test-%s.list' + % params['RELEASE']) + print("filename: %s" % expected_sources_fn) + self.assertTrue(os.path.isfile(expected_sources_fn)) + + # file gets not created, might be permission or env detail + contents = load_tfile_or_url(expected_sources_fn) print(contents) self.assertTrue(1 == 2) -- cgit v1.2.3 From 86c59ffa50a74a1d0001c5ef6ccc78bd6f656fdc Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 10 May 2016 21:03:07 +0200 Subject: use proper asserRaises for try catch --- tests/unittests/test_handler/test_handler_apt_source.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 10a03a8d..a4d359a5 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -132,8 +132,7 @@ class TestAptSourceConfig(TestCase): try: util.subp(('apt-key', 'list', '03683F77')) except util.ProcessExecutionError as err: - print("apt-key failed. " + str(err)) - self.assertTrue(1 == 2) + self.assertRaises(err, "apt-key failed failed") def test_apt_source_ppa(self): @@ -160,7 +159,8 @@ class TestAptSourceConfig(TestCase): # file gets not created, might be permission or env detail contents = load_tfile_or_url(expected_sources_fn) print(contents) - self.assertTrue(1 == 2) + # intentional debug exit + self.assertRaises(ValueError) # vi: ts=4 expandtab -- cgit v1.2.3 From cc56ef479a4cfa4520dfcc7cc27c35bb6ac86bd2 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 10 May 2016 13:18:53 -0700 Subject: Fix up tests and debian distro --- cloudinit/distros/debian.py | 4 ++-- tests/unittests/test_net.py | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) (limited to 'tests') diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index 21a4f805..465413a8 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -27,7 +27,7 @@ from cloudinit import helpers from cloudinit import log as logging from cloudinit import util from cloudinit import net -from cloudinit.net.distros import debian +from cloudinit.net.renderers import eni from cloudinit.distros.parsers.hostname import HostnameConf @@ -57,7 +57,7 @@ class Distro(distros.Distro): # should only happen say once per instance...) self._runner = helpers.Runners(paths) self.osfamily = 'debian' - self._net_renderer = debian.Renderer() + self._net_renderer = eni.Renderer() def apply_locale(self, locale, out_fn=None): if not out_fn: diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index a0cdc493..6daf9601 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -1,5 +1,6 @@ from cloudinit import util from cloudinit import net +from cloudinit.net import cmdline from .helpers import TestCase import base64 @@ -74,15 +75,15 @@ class TestNetConfigParsing(TestCase): "mac_address": "c0:d6:9f:2c:e8:80", "subnets": [{"type": "dhcp"}]}]} - def test_klibc_convert_dhcp(self): - found = net._klibc_to_config_entry(DHCP_CONTENT_1) + def test_cmdline_convert_dhcp(self): + found = cmdline._klibc_to_config_entry(DHCP_CONTENT_1) self.assertEqual(found, ('eth0', DHCP_EXPECTED_1)) - def test_klibc_convert_static(self): - found = net._klibc_to_config_entry(STATIC_CONTENT_1) + def test_cmdline_convert_static(self): + found = cmdline._klibc_to_config_entry(STATIC_CONTENT_1) self.assertEqual(found, ('eth1', STATIC_EXPECTED_1)) - def test_config_from_klibc_net_cfg(self): + def test_config_from_cmdline_net_cfg(self): files = [] pairs = (('net-eth0.cfg', DHCP_CONTENT_1), ('net-eth1.cfg', STATIC_CONTENT_1)) @@ -103,25 +104,25 @@ class TestNetConfigParsing(TestCase): files.append(fp) util.write_file(fp, content) - found = net.config_from_klibc_net_cfg(files=files, mac_addrs=macs) + found = cmdline.config_from_klibc_net_cfg(files=files, + mac_addrs=macs) self.assertEqual(found, expected) def test_cmdline_with_b64(self): data = base64.b64encode(json.dumps(self.simple_cfg).encode()) encoded_text = data.decode() - cmdline = 'ro network-config=' + encoded_text + ' root=foo' - found = net.read_kernel_cmdline_config(cmdline=cmdline) + raw_cmdline = 'ro network-config=' + encoded_text + ' root=foo' + found = cmdline.read_kernel_cmdline_config(cmdline=raw_cmdline) self.assertEqual(found, self.simple_cfg) def test_cmdline_with_b64_gz(self): data = _gzip_data(json.dumps(self.simple_cfg).encode()) encoded_text = base64.b64encode(data).decode() - cmdline = 'ro network-config=' + encoded_text + ' root=foo' - found = net.read_kernel_cmdline_config(cmdline=cmdline) + raw_cmdline = 'ro network-config=' + encoded_text + ' root=foo' + found = cmdline.read_kernel_cmdline_config(cmdline=raw_cmdline) self.assertEqual(found, self.simple_cfg) - def _gzip_data(data): with io.BytesIO() as iobuf: gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf) -- cgit v1.2.3 From c5eb65ed705475640fce1025c74a54052c6e9731 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 10 May 2016 15:12:44 -0700 Subject: Add some basic eni rendering tests --- cloudinit/net/__init__.py | 11 ++++--- cloudinit/net/network_state.py | 2 +- tests/unittests/test_net.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index e911ed0c..0202cbd8 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -107,7 +107,7 @@ class ParserError(Exception): """Raised when parser has issue parsing the interfaces file.""" -def parse_net_config_data(net_config): +def parse_net_config_data(net_config, skip_broken=True): """Parses the config, returns NetworkState object :param net_config: curtin network config dict @@ -116,20 +116,19 @@ def parse_net_config_data(net_config): if 'version' in net_config and 'config' in net_config: ns = network_state.NetworkState(version=net_config.get('version'), config=net_config.get('config')) - ns.parse_config() + ns.parse_config(skip_broken=skip_broken) state = ns.network_state - return state -def parse_net_config(path): +def parse_net_config(path, skip_broken=True): """Parses a curtin network configuration file and return network state""" ns = None net_config = util.read_conf(path) if 'network' in net_config: - ns = parse_net_config_data(net_config.get('network')) - + ns = parse_net_config_data(net_config.get('network'), + skip_broken=skip_broken) return ns diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index 2530a601..2feffa71 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -142,7 +142,7 @@ class NetworkState(object): raise RuntimeError("No handler found for" " command '%s'" % command_type) try: - handler(command) + handler(self, command) except InvalidCommand: if not skip_broken: raise diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 6daf9601..37b48efb 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -1,7 +1,9 @@ from cloudinit import util from cloudinit import net from cloudinit.net import cmdline +from cloudinit.net import eni from .helpers import TestCase +from .helpers import mock import base64 import copy @@ -9,6 +11,8 @@ import io import gzip import json import os +import shutil +import tempfile DHCP_CONTENT_1 = """ DEVICE='eth0' @@ -69,6 +73,70 @@ STATIC_EXPECTED_1 = { } +class TestEniNetRendering(TestCase): + + @mock.patch("cloudinit.net.sys_dev_path") + @mock.patch("cloudinit.net.sys_netdev_info") + @mock.patch("cloudinit.net.get_devicelist") + def test_generation(self, mock_get_devicelist, mock_sys_netdev_info, + mock_sys_dev_path): + mock_get_devicelist.return_value = ['eth1000', 'lo'] + + dev_characteristics = { + 'eth1000': { + "bridge": False, + "carrier": False, + "dormant": False, + "operstate": "down", + "address": "07-1C-C6-75-A4-BE", + } + } + + def netdev_info(name, field): + return dev_characteristics[name][field] + + mock_sys_netdev_info.side_effect = netdev_info + + tmp_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, tmp_dir) + + def sys_dev_path(devname, path=""): + return tmp_dir + devname + "/" + path + + for dev in dev_characteristics: + os.makedirs(os.path.join(tmp_dir, dev)) + with open(os.path.join(tmp_dir, dev, 'operstate'), 'w') as fh: + fh.write("down") + + mock_sys_dev_path.side_effect = sys_dev_path + + network_cfg = net.generate_fallback_config() + network_state = net.parse_net_config_data(network_cfg, + skip_broken=False) + + render_dir = os.path.join(tmp_dir, "render") + os.makedirs(render_dir) + + renderer = eni.Renderer() + renderer.render_network_state(render_dir, network_state, + eni="interfaces", + links_prefix=None, + netrules=None) + + self.assertTrue(os.path.exists(os.path.join(render_dir, + 'interfaces'))) + with open(os.path.join(render_dir, 'interfaces')) as fh: + contents = fh.read() + + expected = """auto lo +iface lo inet loopback + +auto eth1000 +iface eth1000 inet dhcp +""" + self.assertEqual(expected, contents) + + class TestNetConfigParsing(TestCase): simple_cfg = { 'config': [{"type": "physical", "name": "eth0", -- cgit v1.2.3 From 67e506a50dae2b0c1a806f482670b864e84809ae Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 10 May 2016 15:16:05 -0700 Subject: Use lstrip for easier viewing of test expected result --- tests/unittests/test_net.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 37b48efb..005957de 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -128,13 +128,14 @@ class TestEniNetRendering(TestCase): with open(os.path.join(render_dir, 'interfaces')) as fh: contents = fh.read() - expected = """auto lo + expected = """ +auto lo iface lo inet loopback auto eth1000 iface eth1000 inet dhcp """ - self.assertEqual(expected, contents) + self.assertEqual(expected.lstrip(), contents.lstrip()) class TestNetConfigParsing(TestCase): -- cgit v1.2.3 From 4c5d1966ead445dc6d110e9677902b95dfef2dc5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 10:57:14 +0200 Subject: test test_apt_source_key with mocked util.subp --- tests/unittests/test_handler/test_handler_apt_source.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index a4d359a5..29535bee 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -6,6 +6,11 @@ import shutil import tempfile import re +try: + from unittest import mock +except ImportError: + import mock + from cloudinit import distros from cloudinit import util from cloudinit.config import cc_apt_configure @@ -116,11 +121,13 @@ class TestAptSourceConfig(TestCase): 'keyid:': "03683F77", 'filename': self.aptlistfile} - cc_apt_configure.add_sources([cfg], params) + with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: + cc_apt_configure.add_sources([cfg], params) + + mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1234') self.assertTrue(os.path.isfile(self.aptlistfile)) - # report content before making regex contents = load_tfile_or_url(self.aptlistfile) self.assertTrue(re.search(r"%s %s %s %s\n" % ("deb", @@ -128,11 +135,6 @@ class TestAptSourceConfig(TestCase): 'cloud-init-test/ubuntu'), "xenial", "main"), contents, flags=re.IGNORECASE)) - # check if key was imported - try: - util.subp(('apt-key', 'list', '03683F77')) - except util.ProcessExecutionError as err: - self.assertRaises(err, "apt-key failed failed") def test_apt_source_ppa(self): -- cgit v1.2.3 From fef11c6a98ea74774aa84b3b14007b246a1c615e Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 10:57:33 +0200 Subject: fix specification of keyid --- tests/unittests/test_handler/test_handler_apt_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 29535bee..601504bd 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -118,7 +118,7 @@ class TestAptSourceConfig(TestCase): 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' ' xenial main'), - 'keyid:': "03683F77", + 'keyid': "03683F77", 'filename': self.aptlistfile} with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: -- cgit v1.2.3 From 0e299c89e10a6ccb583588ff31ac783421e57501 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 11:03:51 +0200 Subject: convert test_apt_source_ppa to use a mocked util.subp --- tests/unittests/test_handler/test_handler_apt_source.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 601504bd..e73a72c6 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -148,21 +148,13 @@ class TestAptSourceConfig(TestCase): # default matcher needed for ppa matcher = re.compile(r'^[\w-]+:\w').search - cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) + with mock.patch.object(util, 'subp') as mockobj: + cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) + mockobj.assert_called_once_with(['add-apt-repository', + 'ppa:smoser/cloud-init-test']) # adding ppa should ignore filename (uses add-apt-repository) self.assertFalse(os.path.isfile(self.aptlistfile)) - expected_sources_fn=('/etc/apt/sources.list.d/' - 'smoser-ubuntu-cloud-init-test-%s.list' - % params['RELEASE']) - print("filename: %s" % expected_sources_fn) - self.assertTrue(os.path.isfile(expected_sources_fn)) - - # file gets not created, might be permission or env detail - contents = load_tfile_or_url(expected_sources_fn) - print(contents) - # intentional debug exit - self.assertRaises(ValueError) # vi: ts=4 expandtab -- cgit v1.2.3 From 0a35ba5b8d85077a865a137887aa8cfd76405eb2 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 11:06:08 +0200 Subject: fix a few forgotten pep8 warnings in test_handler_apt_source.py --- tests/unittests/test_handler/test_handler_apt_source.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index e73a72c6..849f23c9 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -40,12 +40,14 @@ PACKAGE_MIRRORS = [ GAPMI = distros._get_arch_package_mirror_info + def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url load file and return content after decoding """ return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) + class TestAptSourceConfig(TestCase): """ TestAptSourceConfig Main Class to test apt_source configs @@ -56,7 +58,6 @@ class TestAptSourceConfig(TestCase): self.addCleanup(shutil.rmtree, self.tmp) self.aptlistfile = os.path.join(self.tmp, "single-deb.list") - @staticmethod def _get_default_params(): """ get_default_params @@ -67,7 +68,6 @@ class TestAptSourceConfig(TestCase): params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" return params - def test_apt_source_basic(self): """ test_apt_source_basic Test Fix deb source string, has to overwrite mirror conf in params @@ -89,7 +89,6 @@ class TestAptSourceConfig(TestCase): "main universe multiverse restricted"), contents, flags=re.IGNORECASE)) - def test_apt_source_replacement(self): """ test_apt_source_replace Test Autoreplacement of MIRROR and RELEASE in source specs @@ -108,7 +107,6 @@ class TestAptSourceConfig(TestCase): "multiverse"), contents, flags=re.IGNORECASE)) - def test_apt_source_key(self): """ test_apt_source_key Test specification of a source + key @@ -121,7 +119,8 @@ class TestAptSourceConfig(TestCase): 'keyid': "03683F77", 'filename': self.aptlistfile} - with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: + with mock.patch.object(util, 'subp', + return_value=('fakekey 1234', '')) as mockobj: cc_apt_configure.add_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1234') @@ -136,7 +135,6 @@ class TestAptSourceConfig(TestCase): "xenial", "main"), contents, flags=re.IGNORECASE)) - def test_apt_source_ppa(self): """ test_apt_source_ppa Test specification of a ppa @@ -151,7 +149,7 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp') as mockobj: cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) mockobj.assert_called_once_with(['add-apt-repository', - 'ppa:smoser/cloud-init-test']) + 'ppa:smoser/cloud-init-test']) # adding ppa should ignore filename (uses add-apt-repository) self.assertFalse(os.path.isfile(self.aptlistfile)) -- cgit v1.2.3 From a9cd544d7de1af90f6c5cf9df43135b530acb308 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 16:52:17 +0200 Subject: split test_apt_source_key into one for key and one for keyid --- .../test_handler/test_handler_apt_source.py | 32 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 849f23c9..a7db0fa6 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -107,6 +107,34 @@ class TestAptSourceConfig(TestCase): "multiverse"), contents, flags=re.IGNORECASE)) + def test_apt_source_keyid(self): + """ test_apt_source_keyid + Test specification of a source + keyid + """ + params = self._get_default_params() + cfg = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'keyid': "03683F77", + 'filename': self.aptlistfile} + + with mock.patch.object(util, 'subp', + return_value=('fakekey 1234', '')) as mockobj: + cc_apt_configure.add_sources([cfg], params) + + mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1234') + + self.assertTrue(os.path.isfile(self.aptlistfile)) + + contents = load_tfile_or_url(self.aptlistfile) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", + ('http://ppa.launchpad.net/smoser/' + 'cloud-init-test/ubuntu'), + "xenial", "main"), + contents, flags=re.IGNORECASE)) + def test_apt_source_key(self): """ test_apt_source_key Test specification of a source + key @@ -116,14 +144,14 @@ class TestAptSourceConfig(TestCase): 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' ' xenial main'), - 'keyid': "03683F77", + 'key': "fakekey 4321", 'filename': self.aptlistfile} with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: cc_apt_configure.add_sources([cfg], params) - mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1234') + mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') self.assertTrue(os.path.isfile(self.aptlistfile)) -- cgit v1.2.3 From d82f6c1ecd255ed3a76bd8ef7b76163408f0b398 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 16:55:37 +0200 Subject: add test_apt_source_keyonly (not yet supported) --- tests/unittests/test_handler/test_handler_apt_source.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index a7db0fa6..92a92406 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -163,6 +163,22 @@ class TestAptSourceConfig(TestCase): "xenial", "main"), contents, flags=re.IGNORECASE)) + def test_apt_source_keyonly(self): + """ test_apt_source_keyonly + Test specification key without source (not yet supported) + """ + params = self._get_default_params() + cfg = {'key': "fakekey 4242", + 'filename': self.aptlistfile} + + with mock.patch.object(util, 'subp') as mockobj: + cc_apt_configure.add_sources([cfg], params) + + mockobj.assert_called_once_with(('apt-key', 'add', '-'), 'fakekey 4242') + + # filename should be ignored on key only + self.assertFalse(os.path.isfile(self.aptlistfile)) + def test_apt_source_ppa(self): """ test_apt_source_ppa Test specification of a ppa -- cgit v1.2.3 From b4f76a0a855d792acc05807a3a62cc8c72d80792 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 16:56:12 +0200 Subject: apt_apt_source_key doesn't need a mocked retval --- tests/unittests/test_handler/test_handler_apt_source.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 92a92406..091b07da 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -147,8 +147,7 @@ class TestAptSourceConfig(TestCase): 'key': "fakekey 4321", 'filename': self.aptlistfile} - with mock.patch.object(util, 'subp', - return_value=('fakekey 1234', '')) as mockobj: + with mock.patch.object(util, 'subp') as mockobj: cc_apt_configure.add_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') -- cgit v1.2.3 From d5bc051cdc418efa0a910f8b61790523b89e8f91 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 16:59:15 +0200 Subject: add test_apt_source_keyidonly (not yet supported) --- tests/unittests/test_handler/test_handler_apt_source.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 091b07da..de009174 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -178,6 +178,23 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) + def test_apt_source_keyidonly(self): + """ test_apt_source_keyidonly + Test specification of a keyid without source (not yet supported) + """ + params = self._get_default_params() + cfg = {'keyid': "03683F77", + 'filename': self.aptlistfile} + + with mock.patch.object(util, 'subp', + return_value=('fakekey 1212', '')) as mockobj: + cc_apt_configure.add_sources([cfg], params) + + mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1212') + + # filename should be ignored on key only + self.assertFalse(os.path.isfile(self.aptlistfile)) + def test_apt_source_ppa(self): """ test_apt_source_ppa Test specification of a ppa -- cgit v1.2.3 From da2640951d2d87f38dd539e53115af98f12c11ac Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 11 May 2016 18:44:52 +0200 Subject: fix pep8 warning --- tests/unittests/test_handler/test_handler_apt_source.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index de009174..01d56559 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -173,7 +173,8 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp') as mockobj: cc_apt_configure.add_sources([cfg], params) - mockobj.assert_called_once_with(('apt-key', 'add', '-'), 'fakekey 4242') + mockobj.assert_called_once_with(('apt-key', 'add', '-'), + 'fakekey 4242') # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) -- cgit v1.2.3 From 26ea813d293467921ab6b1e32abd2ab8fcefa3bd Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 11 May 2016 14:18:02 -0700 Subject: Fix py26 for rhel (and older versions of python) --- cloudinit/net/cmdline.py | 26 ++++--- cloudinit/sources/DataSourceAzure.py | 2 +- cloudinit/sources/helpers/openstack.py | 8 +- cloudinit/util.py | 25 ++++++- requirements.txt | 8 +- setup.py | 1 - test-requirements.txt | 1 + tests/unittests/helpers.py | 86 +++------------------- tests/unittests/test__init__.py | 18 ++--- tests/unittests/test_cli.py | 7 +- tests/unittests/test_cs_util.py | 42 ++++------- tests/unittests/test_datasource/test_azure.py | 12 +-- .../unittests/test_datasource/test_azure_helper.py | 12 +-- tests/unittests/test_datasource/test_cloudsigma.py | 26 +++++-- tests/unittests/test_datasource/test_cloudstack.py | 11 +-- .../unittests/test_datasource/test_configdrive.py | 11 +-- tests/unittests/test_datasource/test_nocloud.py | 14 +--- tests/unittests/test_datasource/test_smartos.py | 21 ++++-- tests/unittests/test_net.py | 7 +- tests/unittests/test_reporting.py | 4 +- tests/unittests/test_rh_subscription.py | 23 ++++-- tox.ini | 36 ++++----- 22 files changed, 163 insertions(+), 238 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py index 958c264b..21bc35d9 100644 --- a/cloudinit/net/cmdline.py +++ b/cloudinit/net/cmdline.py @@ -20,7 +20,6 @@ import base64 import glob import gzip import io -import shlex from cloudinit.net import get_devicelist from cloudinit.net import sys_netdev_info @@ -34,13 +33,17 @@ def _load_shell_content(content, add_empty=False, empty_val=None): then add entries in to the returned dictionary for 'VAR=' variables. Set their value to empty_val.""" data = {} - for line in shlex.split(content): - key, value = line.split("=", 1) - if not value: - value = empty_val - if add_empty or value: - data[key] = value - + for line in util.shlex_split(content): + try: + key, value = line.split("=", 1) + except ValueError: + # Unsplittable line, skip it... + pass + else: + if not value: + value = empty_val + if add_empty or value: + data[key] = value return data @@ -60,6 +63,9 @@ def _klibc_to_config_entry(content, mac_addrs=None): if mac_addrs is None: mac_addrs = {} + print("Reading content") + print(content) + data = _load_shell_content(content) try: name = data['DEVICE'] @@ -185,7 +191,7 @@ def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None): return None if mac_addrs is None: - mac_addrs = {k: sys_netdev_info(k, 'address') - for k in get_devicelist()} + mac_addrs = dict((k, sys_netdev_info(k, 'address')) + for k in get_devicelist()) return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs) diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py index 698f4cac..66c8ced8 100644 --- a/cloudinit/sources/DataSourceAzure.py +++ b/cloudinit/sources/DataSourceAzure.py @@ -421,7 +421,7 @@ def write_files(datadir, files, dirmode=None): elem.text = DEF_PASSWD_REDACTION return ET.tostring(root) except Exception: - LOG.critical("failed to redact userpassword in {}".format(fname)) + LOG.critical("failed to redact userpassword in %s", fname) return cnt if not datadir: diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py index 845ea971..b2acc648 100644 --- a/cloudinit/sources/helpers/openstack.py +++ b/cloudinit/sources/helpers/openstack.py @@ -534,13 +534,13 @@ def convert_net_json(network_json): config = [] for link in links: subnets = [] - cfg = {k: v for k, v in link.items() - if k in valid_keys['physical']} + cfg = dict((k, v) for k, v in link.items() + if k in valid_keys['physical']) cfg.update({'name': link['id']}) for network in [net for net in networks if net['link'] == link['id']]: - subnet = {k: v for k, v in network.items() - if k in valid_keys['subnet']} + subnet = dict((k, v) for k, v in network.items() + if k in valid_keys['subnet']) if 'dhcp' in network['type']: t = 'dhcp6' if network['type'].startswith('ipv6') else 'dhcp4' subnet.update({ diff --git a/cloudinit/util.py b/cloudinit/util.py index 0d21e11b..7562b97a 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -37,6 +37,7 @@ import pwd import random import re import shutil +import shlex import socket import stat import string @@ -81,6 +82,7 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], ['lxc-is-container']) PROC_CMDLINE = None +PY26 = sys.version_info[0:2] == (2, 6) def decode_binary(blob, encoding='utf-8'): @@ -171,7 +173,8 @@ class ProcessExecutionError(IOError): def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, - description=None, reason=None): + description=None, reason=None, + errno=None): if not cmd: self.cmd = '-' else: @@ -202,6 +205,7 @@ class ProcessExecutionError(IOError): else: self.reason = '-' + self.errno = errno message = self.MESSAGE_TMPL % { 'description': self.description, 'cmd': self.cmd, @@ -1147,7 +1151,14 @@ def find_devs_with(criteria=None, oformat='device', options.append(path) cmd = blk_id_cmd + options # See man blkid for why 2 is added - (out, _err) = subp(cmd, rcs=[0, 2]) + try: + (out, _err) = subp(cmd, rcs=[0, 2]) + except ProcessExecutionError as e: + if e.errno == errno.ENOENT: + # blkid not found... + out = "" + else: + raise entries = [] for line in out.splitlines(): line = line.strip() @@ -1191,6 +1202,13 @@ def load_file(fname, read_cb=None, quiet=False, decode=True): return contents +def shlex_split(blob): + if PY26 and isinstance(blob, six.text_type): + # Older versions don't support unicode input + blob = blob.encode("utf8") + return shlex.split(blob) + + def get_cmdline(): if 'DEBUG_PROC_CMDLINE' in os.environ: return os.environ["DEBUG_PROC_CMDLINE"] @@ -1696,7 +1714,8 @@ def subp(args, data=None, rcs=None, env=None, capture=True, shell=False, sp = subprocess.Popen(args, **kws) (out, err) = sp.communicate(data) except OSError as e: - raise ProcessExecutionError(cmd=args, reason=e) + raise ProcessExecutionError(cmd=args, reason=e, + errno=e.errno) rc = sp.returncode if rc not in rcs: raise ProcessExecutionError(stdout=out, stderr=err, diff --git a/requirements.txt b/requirements.txt index 19c88857..cc1dc05f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,8 +11,12 @@ PrettyTable oauthlib # This one is currently used only by the CloudSigma and SmartOS datasources. -# If these datasources are removed, this is no longer needed -pyserial +# If these datasources are removed, this is no longer needed. +# +# This will not work in py2.6 so it is only optionally installed on +# python 2.7 and later. +# +# pyserial # This is only needed for places where we need to support configs in a manner # that the built-in config parser is not sufficent (ie diff --git a/setup.py b/setup.py index f86727b2..6b4fc031 100755 --- a/setup.py +++ b/setup.py @@ -197,7 +197,6 @@ requirements = read_requires() if sys.version_info < (3,): requirements.append('cheetah') - setuptools.setup( name='cloud-init', version=get_version(), diff --git a/test-requirements.txt b/test-requirements.txt index 9b3d07c5..d9c757e6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,3 +5,4 @@ pep8==1.5.7 pyflakes contextlib2 setuptools +unittest2 diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index fb9c83a7..33f89254 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -7,12 +7,10 @@ import shutil import tempfile import unittest +import mock import six +import unittest2 -try: - from unittest import mock -except ImportError: - import mock try: from contextlib import ExitStack except ImportError: @@ -21,6 +19,9 @@ except ImportError: from cloudinit import helpers as ch from cloudinit import util +# Used for skipping tests +SkipTest = unittest2.SkipTest + # Used for detecting different python versions PY2 = False PY26 = False @@ -44,79 +45,6 @@ else: if _PY_MINOR == 4 and _PY_MICRO < 3: FIX_HTTPRETTY = True -if PY26: - # For now add these on, taken from python 2.7 + slightly adjusted. Drop - # all this once Python 2.6 is dropped as a minimum requirement. - class TestCase(unittest.TestCase): - def setUp(self): - super(TestCase, self).setUp() - self.__all_cleanups = ExitStack() - - def tearDown(self): - self.__all_cleanups.close() - unittest.TestCase.tearDown(self) - - def addCleanup(self, function, *args, **kws): - self.__all_cleanups.callback(function, *args, **kws) - - def assertIs(self, expr1, expr2, msg=None): - if expr1 is not expr2: - standardMsg = '%r is not %r' % (expr1, expr2) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIn(self, member, container, msg=None): - if member not in container: - standardMsg = '%r not found in %r' % (member, container) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertNotIn(self, member, container, msg=None): - if member in container: - standardMsg = '%r unexpectedly found in %r' - standardMsg = standardMsg % (member, container) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIsNone(self, value, msg=None): - if value is not None: - standardMsg = '%r is not None' - standardMsg = standardMsg % (value) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIsInstance(self, obj, cls, msg=None): - """Same as self.assertTrue(isinstance(obj, cls)), with a nicer - default message.""" - if not isinstance(obj, cls): - standardMsg = '%s is not an instance of %r' % (repr(obj), cls) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertDictContainsSubset(self, expected, actual, msg=None): - missing = [] - mismatched = [] - for k, v in expected.items(): - if k not in actual: - missing.append(k) - elif actual[k] != v: - mismatched.append('%r, expected: %r, actual: %r' - % (k, v, actual[k])) - - if len(missing) == 0 and len(mismatched) == 0: - return - - standardMsg = '' - if missing: - standardMsg = 'Missing: %r' % ','.join(m for m in missing) - if mismatched: - if standardMsg: - standardMsg += '; ' - standardMsg += 'Mismatched values: %s' % ','.join(mismatched) - - self.fail(self._formatMessage(msg, standardMsg)) - - -else: - class TestCase(unittest.TestCase): - pass - - # Makes the old path start # with new base instead of whatever # it previously had @@ -151,6 +79,10 @@ def retarget_many_wrapper(new_base, am, old_func): return wrapper +class TestCase(unittest2.TestCase): + pass + + class ResourceUsingTestCase(TestCase): def setUp(self): super(ResourceUsingTestCase, self).setUp() diff --git a/tests/unittests/test__init__.py b/tests/unittests/test__init__.py index 153f1658..a9b35afe 100644 --- a/tests/unittests/test__init__.py +++ b/tests/unittests/test__init__.py @@ -1,16 +1,7 @@ import os import shutil import tempfile -import unittest - -try: - from unittest import mock -except ImportError: - import mock -try: - from contextlib import ExitStack -except ImportError: - from contextlib2 import ExitStack +import unittest2 from cloudinit import handlers from cloudinit import helpers @@ -18,7 +9,7 @@ from cloudinit import settings from cloudinit import url_helper from cloudinit import util -from .helpers import TestCase +from .helpers import TestCase, ExitStack, mock class FakeModule(handlers.Handler): @@ -99,9 +90,10 @@ class TestWalkerHandleHandler(TestCase): self.assertEqual(self.data['handlercount'], 0) -class TestHandlerHandlePart(unittest.TestCase): +class TestHandlerHandlePart(TestCase): def setUp(self): + super(TestHandlerHandlePart, self).setUp() self.data = "fake data" self.ctype = "fake ctype" self.filename = "fake filename" @@ -177,7 +169,7 @@ class TestHandlerHandlePart(unittest.TestCase): self.data, self.ctype, self.filename, self.payload) -class TestCmdlineUrl(unittest.TestCase): +class TestCmdlineUrl(TestCase): def test_invalid_content(self): url = "http://example.com/foo" key = "mykey" diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py index ed863399..f8fe7c9b 100644 --- a/tests/unittests/test_cli.py +++ b/tests/unittests/test_cli.py @@ -4,12 +4,7 @@ import sys import six from . import helpers as test_helpers - -try: - from unittest import mock -except ImportError: - import mock - +mock = test_helpers.mock BIN_CLOUDINIT = "bin/cloud-init" diff --git a/tests/unittests/test_cs_util.py b/tests/unittests/test_cs_util.py index d7273035..8c9ac0cd 100644 --- a/tests/unittests/test_cs_util.py +++ b/tests/unittests/test_cs_util.py @@ -1,20 +1,14 @@ from __future__ import print_function -import sys -import unittest +from . import helpers as test_helpers -from cloudinit.cs_utils import Cepko +import unittest2 try: - skip = unittest.skip -except AttributeError: - # Python 2.6. Doesn't have to be high fidelity. - def skip(reason): - def decorator(func): - def wrapper(*args, **kws): - print(reason, file=sys.stderr) - return wrapper - return decorator + from cloudinit.cs_utils import Cepko + WILL_WORK = True +except ImportError: + WILL_WORK = False SERVER_CONTEXT = { @@ -32,29 +26,21 @@ SERVER_CONTEXT = { } -class CepkoMock(Cepko): - def all(self): - return SERVER_CONTEXT +if WILL_WORK: + class CepkoMock(Cepko): + def all(self): + return SERVER_CONTEXT - def get(self, key="", request_pattern=None): - return SERVER_CONTEXT['tags'] + def get(self, key="", request_pattern=None): + return SERVER_CONTEXT['tags'] # 2015-01-22 BAW: This test is completely useless because it only ever tests # the CepkoMock object. Even in its original form, I don't think it ever # touched the underlying Cepko class methods. -@skip('This test is completely useless') -class CepkoResultTests(unittest.TestCase): +class CepkoResultTests(test_helpers.TestCase): def setUp(self): - pass - # self.mocked = self.mocker.replace("cloudinit.cs_utils.Cepko", - # spec=CepkoMock, - # count=False, - # passthrough=False) - # self.mocked() - # self.mocker.result(CepkoMock()) - # self.mocker.replay() - # self.c = Cepko() + raise unittest2.SkipTest('This test is completely useless') def test_getitem(self): result = self.c.all() diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py index 444e2799..aafe1bc2 100644 --- a/tests/unittests/test_datasource/test_azure.py +++ b/tests/unittests/test_datasource/test_azure.py @@ -1,16 +1,8 @@ from cloudinit import helpers from cloudinit.util import b64e, decode_binary, load_file from cloudinit.sources import DataSourceAzure -from ..helpers import TestCase, populate_dir -try: - from unittest import mock -except ImportError: - import mock -try: - from contextlib import ExitStack -except ImportError: - from contextlib2 import ExitStack +from ..helpers import TestCase, populate_dir, mock, ExitStack, PY26, SkipTest import crypt import os @@ -83,6 +75,8 @@ class TestAzureDataSource(TestCase): def setUp(self): super(TestAzureDataSource, self).setUp() + if PY26: + raise SkipTest("Does not work on python 2.6") self.tmp = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmp) diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py index 1134199b..736f4463 100644 --- a/tests/unittests/test_datasource/test_azure_helper.py +++ b/tests/unittests/test_datasource/test_azure_helper.py @@ -1,17 +1,11 @@ import os from cloudinit.sources.helpers import azure as azure_helper -from ..helpers import TestCase -try: - from unittest import mock -except ImportError: - import mock +from ..helpers import ExitStack +from ..helpers import TestCase -try: - from contextlib import ExitStack -except ImportError: - from contextlib2 import ExitStack +from ..helpers import mock GOAL_STATE_TEMPLATE = """\ diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py index 772d189a..11968796 100644 --- a/tests/unittests/test_datasource/test_cloudsigma.py +++ b/tests/unittests/test_datasource/test_cloudsigma.py @@ -1,11 +1,18 @@ # coding: utf-8 + import copy -from cloudinit.cs_utils import Cepko -from cloudinit.sources import DataSourceCloudSigma +try: + # Serial does not work on py2.6 (anymore) + import pyserial + from cloudinit.cs_utils import Cepko + from cloudinit.sources import DataSourceCloudSigma + WILL_WORK = True +except ImportError: + WILL_WORK = False from .. import helpers as test_helpers - +from ..helpers import SkipTest SERVER_CONTEXT = { "cpu": 1000, @@ -29,17 +36,20 @@ SERVER_CONTEXT = { } -class CepkoMock(Cepko): - def __init__(self, mocked_context): - self.result = mocked_context +if WILL_WORK: + class CepkoMock(Cepko): + def __init__(self, mocked_context): + self.result = mocked_context - def all(self): - return self + def all(self): + return self class DataSourceCloudSigmaTest(test_helpers.TestCase): def setUp(self): super(DataSourceCloudSigmaTest, self).setUp() + if not WILL_WORK: + raise SkipTest("Datasource testing not supported") self.datasource = DataSourceCloudSigma.DataSourceCloudSigma("", "", "") self.datasource.is_running_in_cloudsigma = lambda: True self.datasource.cepko = CepkoMock(SERVER_CONTEXT) diff --git a/tests/unittests/test_datasource/test_cloudstack.py b/tests/unittests/test_datasource/test_cloudstack.py index 656d80d1..4d6b47b4 100644 --- a/tests/unittests/test_datasource/test_cloudstack.py +++ b/tests/unittests/test_datasource/test_cloudstack.py @@ -1,15 +1,6 @@ from cloudinit import helpers from cloudinit.sources.DataSourceCloudStack import DataSourceCloudStack -from ..helpers import TestCase - -try: - from unittest import mock -except ImportError: - import mock -try: - from contextlib import ExitStack -except ImportError: - from contextlib2 import ExitStack +from ..helpers import TestCase, mock, ExitStack class TestCloudStackPasswordFetching(TestCase): diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index 8beaf95e..14cc8266 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -5,22 +5,13 @@ import shutil import six import tempfile -try: - from unittest import mock -except ImportError: - import mock -try: - from contextlib import ExitStack -except ImportError: - from contextlib2 import ExitStack - from cloudinit import helpers from cloudinit import settings from cloudinit.sources import DataSourceConfigDrive as ds from cloudinit.sources.helpers import openstack from cloudinit import util -from ..helpers import TestCase +from ..helpers import TestCase, ExitStack, mock PUBKEY = u'ssh-rsa AAAAB3NzaC1....sIkJhq8wdX+4I3A4cYbYP ubuntu@server-460\n' diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py index 2d5fc37c..a92dd3b3 100644 --- a/tests/unittests/test_datasource/test_nocloud.py +++ b/tests/unittests/test_datasource/test_nocloud.py @@ -1,22 +1,12 @@ from cloudinit import helpers from cloudinit.sources import DataSourceNoCloud from cloudinit import util -from ..helpers import TestCase, populate_dir +from ..helpers import TestCase, populate_dir, mock, ExitStack import os import yaml import shutil import tempfile -import unittest - -try: - from unittest import mock -except ImportError: - import mock -try: - from contextlib import ExitStack -except ImportError: - from contextlib2 import ExitStack class TestNoCloudDataSource(TestCase): @@ -139,7 +129,7 @@ class TestNoCloudDataSource(TestCase): self.assertTrue(ret) -class TestParseCommandLineData(unittest.TestCase): +class TestParseCommandLineData(TestCase): def test_parse_cmdline_data_valid(self): ds_id = "ds=nocloud" diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py index 5c49966a..6b628276 100644 --- a/tests/unittests/test_datasource/test_smartos.py +++ b/tests/unittests/test_datasource/test_smartos.py @@ -33,19 +33,21 @@ import tempfile import uuid from binascii import crc32 -import serial +try: + # Serial does not work on py2.6 (anymore) + import serial + from cloudinit.sources import DataSourceSmartOS + WILL_WORK = True +except ImportError: + WILL_WORK = False + import six from cloudinit import helpers as c_helpers -from cloudinit.sources import DataSourceSmartOS from cloudinit.util import b64e from .. import helpers - -try: - from unittest import mock -except ImportError: - import mock +from ..helpers import mock, SkipTest MOCK_RETURNS = { 'hostname': 'test-host', @@ -79,7 +81,8 @@ def get_mock_client(mockdata): class TestSmartOSDataSource(helpers.FilesystemMockingTestCase): def setUp(self): super(TestSmartOSDataSource, self).setUp() - + if not WILL_WORK: + raise SkipTest("This test will not work") self.tmp = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmp) self.legacy_user_d = tempfile.mkdtemp() @@ -445,6 +448,8 @@ class TestJoyentMetadataClient(helpers.FilesystemMockingTestCase): def setUp(self): super(TestJoyentMetadataClient, self).setUp() + if not WILL_WORK: + raise SkipTest("This test will not work") self.serial = mock.MagicMock(spec=serial.Serial) self.request_id = 0xabcdef12 self.metadata_value = 'value' diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 005957de..ed2c6d0f 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -78,8 +78,9 @@ class TestEniNetRendering(TestCase): @mock.patch("cloudinit.net.sys_dev_path") @mock.patch("cloudinit.net.sys_netdev_info") @mock.patch("cloudinit.net.get_devicelist") - def test_generation(self, mock_get_devicelist, mock_sys_netdev_info, - mock_sys_dev_path): + def test_default_generation(self, mock_get_devicelist, + mock_sys_netdev_info, + mock_sys_dev_path): mock_get_devicelist.return_value = ['eth1000', 'lo'] dev_characteristics = { @@ -138,7 +139,7 @@ iface eth1000 inet dhcp self.assertEqual(expected.lstrip(), contents.lstrip()) -class TestNetConfigParsing(TestCase): +class TestCmdlineConfigParsing(TestCase): simple_cfg = { 'config': [{"type": "physical", "name": "eth0", "mac_address": "c0:d6:9f:2c:e8:80", diff --git a/tests/unittests/test_reporting.py b/tests/unittests/test_reporting.py index 32356ef9..493bb261 100644 --- a/tests/unittests/test_reporting.py +++ b/tests/unittests/test_reporting.py @@ -7,7 +7,9 @@ from cloudinit import reporting from cloudinit.reporting import handlers from cloudinit.reporting import events -from .helpers import (mock, TestCase) +import mock + +from .helpers import TestCase def _fake_registry(): diff --git a/tests/unittests/test_rh_subscription.py b/tests/unittests/test_rh_subscription.py index 8c586ad7..13045aaf 100644 --- a/tests/unittests/test_rh_subscription.py +++ b/tests/unittests/test_rh_subscription.py @@ -1,11 +1,24 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging + from cloudinit import util from cloudinit.config import cc_rh_subscription -import logging -import mock -import unittest + +from .helpers import TestCase, mock -class GoodTests(unittest.TestCase): +class GoodTests(TestCase): def setUp(self): super(GoodTests, self).setUp() self.name = "cc_rh_subscription" @@ -92,7 +105,7 @@ class GoodTests(unittest.TestCase): self.assertEqual(self.SM._sub_man_cli.call_count, 9) -class TestBadInput(unittest.TestCase): +class TestBadInput(TestCase): name = "cc_rh_subscription" cloud_init = None log = logging.getLogger("bad_tests") diff --git a/tox.ini b/tox.ini index bd7c27dd..3210b0ee 100644 --- a/tox.ini +++ b/tox.ini @@ -1,32 +1,32 @@ [tox] -envlist = py27,py3,pyflakes +envlist = py27,py26,py3,pyflakes recreate = True +usedevelop = True [testenv] commands = python -m nose {posargs:tests} deps = -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt - -[testenv:py3] -basepython = python3 + -r{toxinidir}/requirements.txt +setenv = + LC_ALL = en_US.utf-8 [testenv:pyflakes] basepython = python3 commands = {envpython} -m pyflakes {posargs:cloudinit/ tests/ tools/} - {envpython} -m pep8 {posargs:cloudinit/ tests/ tools/} + {envpython} -m pep8 {posargs:cloudinit/ tests/ tools/} -# https://github.com/gabrielfalcao/HTTPretty/issues/223 -setenv = - LC_ALL = en_US.utf-8 +[testenv:py3] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt + pyserial + +[testenv:py27] +deps = -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt + pyserial [testenv:py26] commands = nosetests {posargs:tests} -deps = - contextlib2 - httpretty>=0.7.1 - mock - nose - pep8==1.5.7 - pyflakes -setenv = - LC_ALL = C +deps = -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt -- cgit v1.2.3 From 12d7ee2cb6589b866ab26b508b15c65326481d6c Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 11 May 2016 16:47:50 -0700 Subject: Use a fake serial module that will allow tests to contine Instead of aborting all serial using tests instead just create a serial module in cloudinit that will create a fake and broken serial class when pyserial is not actually installed. This allows for using the datasource and tests that exist in a more functional and tested manner (even when pyserial is not found). --- cloudinit/cs_utils.py | 3 +- cloudinit/serial.py | 50 ++++++++++++++++++++++ cloudinit/sources/DataSourceSmartOS.py | 4 +- tests/unittests/test_cs_util.py | 21 +++------ tests/unittests/test_datasource/test_cloudsigma.py | 23 +++------- tests/unittests/test_datasource/test_smartos.py | 15 ++----- 6 files changed, 71 insertions(+), 45 deletions(-) create mode 100644 cloudinit/serial.py (limited to 'tests') diff --git a/cloudinit/cs_utils.py b/cloudinit/cs_utils.py index 83ac1a0e..412431f2 100644 --- a/cloudinit/cs_utils.py +++ b/cloudinit/cs_utils.py @@ -33,7 +33,8 @@ API Docs: http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html import json import platform -import serial +from cloudinit import serial + # these high timeouts are necessary as read may read a lot of data. READ_TIMEOUT = 60 diff --git a/cloudinit/serial.py b/cloudinit/serial.py new file mode 100644 index 00000000..af45c13e --- /dev/null +++ b/cloudinit/serial.py @@ -0,0 +1,50 @@ +# vi: ts=4 expandtab +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from __future__ import absolute_import + +try: + from serial import Serial +except ImportError: + # For older versions of python (ie 2.6) pyserial may not exist and/or + # work and/or be installed, so make a dummy/fake serial that blows up + # when used... + class Serial(object): + def __init__(self, *args, **kwargs): + pass + + @staticmethod + def isOpen(): + return False + + @staticmethod + def write(data): + raise IOError("Unable to perform serial `write` operation," + " pyserial not installed.") + + @staticmethod + def readline(): + raise IOError("Unable to perform serial `readline` operation," + " pyserial not installed.") + + @staticmethod + def flush(): + raise IOError("Unable to perform serial `flush` operation," + " pyserial not installed.") + + @staticmethod + def read(size=1): + raise IOError("Unable to perform serial `read` operation," + " pyserial not installed.") diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py index 6cbd8dfa..c7641eb3 100644 --- a/cloudinit/sources/DataSourceSmartOS.py +++ b/cloudinit/sources/DataSourceSmartOS.py @@ -40,13 +40,11 @@ import re import socket import stat -import serial - from cloudinit import log as logging +from cloudinit import serial from cloudinit import sources from cloudinit import util - LOG = logging.getLogger(__name__) SMARTOS_ATTRIB_MAP = { diff --git a/tests/unittests/test_cs_util.py b/tests/unittests/test_cs_util.py index 8c9ac0cd..56c9ce9e 100644 --- a/tests/unittests/test_cs_util.py +++ b/tests/unittests/test_cs_util.py @@ -2,13 +2,7 @@ from __future__ import print_function from . import helpers as test_helpers -import unittest2 - -try: - from cloudinit.cs_utils import Cepko - WILL_WORK = True -except ImportError: - WILL_WORK = False +from cloudinit.cs_utils import Cepko SERVER_CONTEXT = { @@ -26,13 +20,12 @@ SERVER_CONTEXT = { } -if WILL_WORK: - class CepkoMock(Cepko): - def all(self): - return SERVER_CONTEXT +class CepkoMock(Cepko): + def all(self): + return SERVER_CONTEXT - def get(self, key="", request_pattern=None): - return SERVER_CONTEXT['tags'] + def get(self, key="", request_pattern=None): + return SERVER_CONTEXT['tags'] # 2015-01-22 BAW: This test is completely useless because it only ever tests @@ -40,7 +33,7 @@ if WILL_WORK: # touched the underlying Cepko class methods. class CepkoResultTests(test_helpers.TestCase): def setUp(self): - raise unittest2.SkipTest('This test is completely useless') + raise test_helpers.SkipTest('This test is completely useless') def test_getitem(self): result = self.c.all() diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py index 11968796..7950fc52 100644 --- a/tests/unittests/test_datasource/test_cloudsigma.py +++ b/tests/unittests/test_datasource/test_cloudsigma.py @@ -2,14 +2,8 @@ import copy -try: - # Serial does not work on py2.6 (anymore) - import pyserial - from cloudinit.cs_utils import Cepko - from cloudinit.sources import DataSourceCloudSigma - WILL_WORK = True -except ImportError: - WILL_WORK = False +from cloudinit.cs_utils import Cepko +from cloudinit.sources import DataSourceCloudSigma from .. import helpers as test_helpers from ..helpers import SkipTest @@ -36,20 +30,17 @@ SERVER_CONTEXT = { } -if WILL_WORK: - class CepkoMock(Cepko): - def __init__(self, mocked_context): - self.result = mocked_context +class CepkoMock(Cepko): + def __init__(self, mocked_context): + self.result = mocked_context - def all(self): - return self + def all(self): + return self class DataSourceCloudSigmaTest(test_helpers.TestCase): def setUp(self): super(DataSourceCloudSigmaTest, self).setUp() - if not WILL_WORK: - raise SkipTest("Datasource testing not supported") self.datasource = DataSourceCloudSigma.DataSourceCloudSigma("", "", "") self.datasource.is_running_in_cloudsigma = lambda: True self.datasource.cepko = CepkoMock(SERVER_CONTEXT) diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py index 6b628276..f536ef4f 100644 --- a/tests/unittests/test_datasource/test_smartos.py +++ b/tests/unittests/test_datasource/test_smartos.py @@ -33,13 +33,8 @@ import tempfile import uuid from binascii import crc32 -try: - # Serial does not work on py2.6 (anymore) - import serial - from cloudinit.sources import DataSourceSmartOS - WILL_WORK = True -except ImportError: - WILL_WORK = False +from cloudinit import serial +from cloudinit.sources import DataSourceSmartOS import six @@ -81,8 +76,7 @@ def get_mock_client(mockdata): class TestSmartOSDataSource(helpers.FilesystemMockingTestCase): def setUp(self): super(TestSmartOSDataSource, self).setUp() - if not WILL_WORK: - raise SkipTest("This test will not work") + self.tmp = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmp) self.legacy_user_d = tempfile.mkdtemp() @@ -448,8 +442,7 @@ class TestJoyentMetadataClient(helpers.FilesystemMockingTestCase): def setUp(self): super(TestJoyentMetadataClient, self).setUp() - if not WILL_WORK: - raise SkipTest("This test will not work") + self.serial = mock.MagicMock(spec=serial.Serial) self.request_id = 0xabcdef12 self.metadata_value = 'value' -- cgit v1.2.3 From 48a50dabf482a8ae029775a94be2d1b84763f98d Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 09:52:02 +0200 Subject: remove unused test parts in test_handler_apt_source --- .../test_handler/test_handler_apt_source.py | 23 ---------------------- 1 file changed, 23 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 01d56559..38c93e0e 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -17,29 +17,6 @@ from cloudinit.config import cc_apt_configure from ..helpers import TestCase -UNKNOWN_ARCH_INFO = { - 'arches': ['default'], - 'failsafe': {'primary': 'http://fs-primary-default', - 'security': 'http://fs-security-default'} -} - -PACKAGE_MIRRORS = [ - {'arches': ['i386', 'amd64'], - 'failsafe': {'primary': 'http://fs-primary-intel', - 'security': 'http://fs-security-intel'}, - 'search': { - 'primary': ['http://%(ec2_region)s.ec2/', - 'http://%(availability_zone)s.clouds/'], - 'security': ['http://security-mirror1-intel', - 'http://security-mirror2-intel']}}, - {'arches': ['armhf', 'armel'], - 'failsafe': {'primary': 'http://fs-primary-arm', - 'security': 'http://fs-security-arm'}}, - UNKNOWN_ARCH_INFO -] - -GAPMI = distros._get_arch_package_mirror_info - def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url -- cgit v1.2.3 From 1b4a06cd7933f876b16576567bbc0dab46b83b6b Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 13:21:29 +0200 Subject: add testcase for rendering of templates into source.list --- .../test_handler_apt_configure_sources_list.py | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/unittests/test_handler/test_handler_apt_configure_sources_list.py (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py new file mode 100644 index 00000000..46edb628 --- /dev/null +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -0,0 +1,89 @@ +""" test_handler_apt_configure_sources_list +Test templating of sources list +""" +import os +import shutil +import tempfile +import re + +import logging + +try: + from unittest import mock +except ImportError: + import mock + +from cloudinit import cloud +from cloudinit import distros +from cloudinit import util +from cloudinit import helpers +from cloudinit import templater + +from cloudinit.sources import DataSourceNone +from cloudinit.config import cc_apt_configure + +from .. import helpers as t_help + +LOG = logging.getLogger(__name__) + + +def load_tfile_or_url(*args, **kwargs): + """ load_tfile_or_url + load file and return content after decoding + """ + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) + + +class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): + """ TestAptSourceConfigSourceList + Main Class to test sources list rendering + """ + def setUp(self): + super(TestAptSourceConfigSourceList, self).setUp() + self.new_root = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.new_root) + + def _get_cloud(self, distro, metadata=None): + self.patchUtils(self.new_root) + paths = helpers.Paths({}) + cls = distros.fetch(distro) + mydist = cls(distro, {}, paths) + myds = DataSourceNone.DataSourceNone({}, mydist, paths) + if metadata: + myds.metadata.update(metadata) + return cloud.Cloud(myds, paths, {}, mydist, None) + +# TODO - Ubuntu template +# TODO - Debian template +# TODO Later - custom template filename +# TODO Later - custom template raw + + def test_apt_source_list_ubuntu(self): + """ test_apt_source_list + Test rendering of a source.list from template for ubuntu + """ + self.patchOS(self.new_root) + self.patchUtils(self.new_root) + + cfg = {'apt_mirror': 'http://archive.ubuntu.com/ubuntu/'} + mycloud = self._get_cloud('ubuntu') + + with mock.patch.object(templater, 'render_to_file') as mocktmpl: + with mock.patch.object(os.path, 'isfile', + return_value=True) as mockisfile: + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) + + mockisfile.assert_any_call(('/etc/cloud/templates/' + 'sources.list.ubuntu.tmpl')) + mocktmpl.assert_called_once_with(('/etc/cloud/templates/' + 'sources.list.ubuntu.tmpl'), + '/etc/apt/sources.list', + {'codename': '', + 'primary': + 'http://archive.ubuntu.com/ubuntu/', + 'mirror': + 'http://archive.ubuntu.com/ubuntu/'}) + + +# vi: ts=4 expandtab -- cgit v1.2.3 From 83f2351bba273273d04759ebacd2a25df9045090 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 13:28:36 +0200 Subject: test debian and ubuntu source.list templating --- .../test_handler_apt_configure_sources_list.py | 32 ++++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 46edb628..aff272a3 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -58,15 +58,15 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): # TODO Later - custom template filename # TODO Later - custom template raw - def test_apt_source_list_ubuntu(self): - """ test_apt_source_list - Test rendering of a source.list from template for ubuntu + def apt_source_list(self, distro, mirror): + """ apt_source_list + Test rendering of a source.list from template for a given distro """ self.patchOS(self.new_root) self.patchUtils(self.new_root) - cfg = {'apt_mirror': 'http://archive.ubuntu.com/ubuntu/'} - mycloud = self._get_cloud('ubuntu') + cfg = {'apt_mirror': mirror} + mycloud = self._get_cloud(distro) with mock.patch.object(templater, 'render_to_file') as mocktmpl: with mock.patch.object(os.path, 'isfile', @@ -75,15 +75,29 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): LOG, None) mockisfile.assert_any_call(('/etc/cloud/templates/' - 'sources.list.ubuntu.tmpl')) + 'sources.list.%s.tmpl' % distro)) mocktmpl.assert_called_once_with(('/etc/cloud/templates/' - 'sources.list.ubuntu.tmpl'), + 'sources.list.%s.tmpl' % distro), '/etc/apt/sources.list', {'codename': '', 'primary': - 'http://archive.ubuntu.com/ubuntu/', + mirror, 'mirror': - 'http://archive.ubuntu.com/ubuntu/'}) + mirror}) + + + def test_apt_source_list_ubuntu(self): + """ test_apt_source_list_ubuntu + Test rendering of a source.list from template for ubuntu + """ + self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') + + + def test_apt_source_list_debian(self): + """ test_apt_source_list_debian + Test rendering of a source.list from template for debian + """ + self.apt_source_list('debian', 'ftp.us.debian.org') # vi: ts=4 expandtab -- cgit v1.2.3 From 6fc583a4bcb49f7dbecdac095bc63e90dd6edaf3 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 13:43:59 +0200 Subject: test mirror list with failing mirror --- cloudinit/config/cc_apt_configure.py | 1 + .../test_handler_apt_configure_sources_list.py | 23 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index 1d3eddff..ccbdcbc1 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -57,6 +57,7 @@ def handle(name, cfg, cloud, log, _args): release = get_release() mirrors = find_apt_mirror_info(cloud, cfg) + print(mirrors) if not mirrors or "primary" not in mirrors: log.debug(("Skipping module named %s," " no package 'mirror' located"), name) diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index aff272a3..bac2da24 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -58,14 +58,20 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): # TODO Later - custom template filename # TODO Later - custom template raw - def apt_source_list(self, distro, mirror): + def apt_source_list(self, distro, mirror, mirrorcheck=None): """ apt_source_list Test rendering of a source.list from template for a given distro """ self.patchOS(self.new_root) self.patchUtils(self.new_root) - cfg = {'apt_mirror': mirror} + if mirrorcheck is None: + mirrorcheck = mirror + + if isinstance(mirror, list): + cfg = {'apt_mirror_search': mirror} + else: + cfg = {'apt_mirror': mirror} mycloud = self._get_cloud(distro) with mock.patch.object(templater, 'render_to_file') as mocktmpl: @@ -81,9 +87,9 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): '/etc/apt/sources.list', {'codename': '', 'primary': - mirror, + mirrorcheck, 'mirror': - mirror}) + mirrorcheck}) def test_apt_source_list_ubuntu(self): @@ -100,4 +106,13 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): self.apt_source_list('debian', 'ftp.us.debian.org') + def test_apt_srcl_ubuntu_mirrorfail(self): + """ test_apt_source_list_ubuntu_mirrorfail + Test rendering of a source.list from template for ubuntu + """ + self.apt_source_list('ubuntu', ['http://does.not.exist', + 'http://archive.ubuntu.com/ubuntu/'], + 'http://archive.ubuntu.com/ubuntu/') + + # vi: ts=4 expandtab -- cgit v1.2.3 From d2af10ab5d3fef34934c04d4590ea611204f44c5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 13:44:29 +0200 Subject: order code in test order --- .../test_handler_apt_configure_sources_list.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index bac2da24..d48167c9 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -92,13 +92,6 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): mirrorcheck}) - def test_apt_source_list_ubuntu(self): - """ test_apt_source_list_ubuntu - Test rendering of a source.list from template for ubuntu - """ - self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') - - def test_apt_source_list_debian(self): """ test_apt_source_list_debian Test rendering of a source.list from template for debian @@ -106,6 +99,13 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): self.apt_source_list('debian', 'ftp.us.debian.org') + def test_apt_source_list_ubuntu(self): + """ test_apt_source_list_ubuntu + Test rendering of a source.list from template for ubuntu + """ + self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') + + def test_apt_srcl_ubuntu_mirrorfail(self): """ test_apt_source_list_ubuntu_mirrorfail Test rendering of a source.list from template for ubuntu -- cgit v1.2.3 From 18864f8e7331da359399decb1b080e36fa343f5a Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 13:45:15 +0200 Subject: remove missed test print --- cloudinit/config/cc_apt_configure.py | 1 - .../test_handler/test_handler_apt_configure_sources_list.py | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index ccbdcbc1..1d3eddff 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -57,7 +57,6 @@ def handle(name, cfg, cloud, log, _args): release = get_release() mirrors = find_apt_mirror_info(cloud, cfg) - print(mirrors) if not mirrors or "primary" not in mirrors: log.debug(("Skipping module named %s," " no package 'mirror' located"), name) diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index d48167c9..b8fe03ae 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -106,6 +106,15 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') + def test_apt_srcl_debian_mirrorfail(self): + """ test_apt_source_list_debian_mirrorfail + Test rendering of a source.list from template for debian + """ + self.apt_source_list('debian', ['http://does.not.exist', + 'ftp.us.debian.org'], + 'ftp.us.debian.org') + + def test_apt_srcl_ubuntu_mirrorfail(self): """ test_apt_source_list_ubuntu_mirrorfail Test rendering of a source.list from template for ubuntu -- cgit v1.2.3 From dc2b81f3f6922c678806ce9120e9ce4c590243dd Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 14:09:17 +0200 Subject: use recommended http mirror redirection for debian --- .../test_handler/test_handler_apt_configure_sources_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index b8fe03ae..5255c5b9 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -96,7 +96,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """ test_apt_source_list_debian Test rendering of a source.list from template for debian """ - self.apt_source_list('debian', 'ftp.us.debian.org') + self.apt_source_list('debian', 'http://httpredir.debian.org/debian') def test_apt_source_list_ubuntu(self): @@ -111,8 +111,8 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): Test rendering of a source.list from template for debian """ self.apt_source_list('debian', ['http://does.not.exist', - 'ftp.us.debian.org'], - 'ftp.us.debian.org') + 'http://httpredir.debian.org/debian'], + 'http://httpredir.debian.org/debian') def test_apt_srcl_ubuntu_mirrorfail(self): -- cgit v1.2.3 From 9a75e200402410e6f0ae841cdadebb20840c6a8e Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 14:30:11 +0200 Subject: initial version of a test for a custom source.list template --- .../test_handler_apt_configure_sources_list.py | 49 +++++++++++++++++++--- 1 file changed, 44 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 5255c5b9..8777d2a9 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -26,6 +26,24 @@ from .. import helpers as t_help LOG = logging.getLogger(__name__) +YAML_TEXT_CUSTOM_SL = """ +apt_mirror: http://archive.ubuntu.com/ubuntu/ +apt_custom_sources_list: | + ## template:jinja + ## Note, this file is written by cloud-init on first boot of an instance + ## modifications made here will not survive a re-bundle. + ## if you wish to make changes you can: + ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg + ## or do the same in user-data + ## b.) add sources in /etc/apt/sources.list.d + ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl + + # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to + # newer versions of the distribution. + deb {{mirror}} {{codename}} main restricted + deb-src {{mirror}} {{codename}} main restricted + # FIND_SOMETHING_SPECIAL +""" def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url @@ -53,11 +71,6 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): myds.metadata.update(metadata) return cloud.Cloud(myds, paths, {}, mydist, None) -# TODO - Ubuntu template -# TODO - Debian template -# TODO Later - custom template filename -# TODO Later - custom template raw - def apt_source_list(self, distro, mirror, mirrorcheck=None): """ apt_source_list Test rendering of a source.list from template for a given distro @@ -124,4 +137,30 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): 'http://archive.ubuntu.com/ubuntu/') + def test_apt_srcl_custom(self): + """ test_apt_srcl_custom + Test rendering from a custom source.list template + """ + self.patchOS(self.new_root) + self.patchUtils(self.new_root) + + cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) + mycloud = self._get_cloud('ubuntu') + mirrorcheck = 'http://archive.ubuntu.com/ubuntu/' + + with mock.patch.object(templater, 'render_to_file') as mocktmpl: + with mock.patch.object(os.path, 'isfile', + return_value=True) as mockisfile: + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) + + mockisfile.assert_any_call(('/etc/cloud/templates/sources.list.ubuntu.tmpl')) + mocktmpl.assert_called_once_with(('/etc/cloud/templates/sources.list.ubuntu.tmpl'), + '/etc/apt/sources.list', + {'codename': '', + 'primary': + mirrorcheck, + 'mirror': + mirrorcheck}) + # vi: ts=4 expandtab -- cgit v1.2.3 From c6cbba7184f0864f34b90532a4b0d3d61fdd6bc9 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 15:42:31 +0200 Subject: Finalize test_apt_srcl_custom Adding: - known content after full templating - restore subp from mocking for proper execution - drop all kind of superfluous mocking of util and OS --- .../test_handler_apt_configure_sources_list.py | 41 +++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 8777d2a9..c1ca71af 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -58,6 +58,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """ def setUp(self): super(TestAptSourceConfigSourceList, self).setUp() + self.subp = util.subp self.new_root = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.new_root) @@ -75,9 +76,6 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """ apt_source_list Test rendering of a source.list from template for a given distro """ - self.patchOS(self.new_root) - self.patchUtils(self.new_root) - if mirrorcheck is None: mirrorcheck = mirror @@ -141,26 +139,29 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """ test_apt_srcl_custom Test rendering from a custom source.list template """ - self.patchOS(self.new_root) - self.patchUtils(self.new_root) - cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) mycloud = self._get_cloud('ubuntu') - mirrorcheck = 'http://archive.ubuntu.com/ubuntu/' - with mock.patch.object(templater, 'render_to_file') as mocktmpl: - with mock.patch.object(os.path, 'isfile', - return_value=True) as mockisfile: - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) + # the second mock restores the original subp + with mock.patch.object(util, 'write_file') as mockwrite, \ + mock.patch.object(util, 'subp', self.subp) as mocksubp: + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) + + mockwrite.assert_called_once_with( + '/etc/apt/sources.list', + ("## Note, this file is written by cloud-init on first boot of an" + " instance\n## modifications made here will not survive a re-bun" + "dle.\n## if you wish to make changes you can:\n## a.) add 'apt_" + "preserve_sources_list: true' to /etc/cloud/cloud.cfg\n## or" + " do the same in user-data\n## b.) add sources in /etc/apt/sourc" + "es.list.d\n## c.) make changes to template file /etc/cloud/temp" + "lates/sources.list.tmpl\n\n# See http://help.ubuntu.com/communi" + "ty/UpgradeNotes for how to upgrade to\n# newer versions of the " + "distribution.\ndeb http://archive.ubuntu.com/ubuntu/ xenial mai" + "n restricted\ndeb-src http://archive.ubuntu.com/ubuntu/ xenial " + "main restricted\n# FIND_SOMETHING_SPECIAL\n"), + mode=420) - mockisfile.assert_any_call(('/etc/cloud/templates/sources.list.ubuntu.tmpl')) - mocktmpl.assert_called_once_with(('/etc/cloud/templates/sources.list.ubuntu.tmpl'), - '/etc/apt/sources.list', - {'codename': '', - 'primary': - mirrorcheck, - 'mirror': - mirrorcheck}) # vi: ts=4 expandtab -- cgit v1.2.3 From cdee96acdde84071d54764e2554e6ee27702166c Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 15:51:00 +0200 Subject: use old style nested context to make pep8 happy --- .../test_handler_apt_configure_sources_list.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index c1ca71af..16d6a5d1 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -45,6 +45,7 @@ apt_custom_sources_list: | # FIND_SOMETHING_SPECIAL """ + def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url load file and return content after decoding @@ -102,21 +103,18 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): 'mirror': mirrorcheck}) - def test_apt_source_list_debian(self): """ test_apt_source_list_debian Test rendering of a source.list from template for debian """ self.apt_source_list('debian', 'http://httpredir.debian.org/debian') - def test_apt_source_list_ubuntu(self): """ test_apt_source_list_ubuntu Test rendering of a source.list from template for ubuntu """ self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') - def test_apt_srcl_debian_mirrorfail(self): """ test_apt_source_list_debian_mirrorfail Test rendering of a source.list from template for debian @@ -125,7 +123,6 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): 'http://httpredir.debian.org/debian'], 'http://httpredir.debian.org/debian') - def test_apt_srcl_ubuntu_mirrorfail(self): """ test_apt_source_list_ubuntu_mirrorfail Test rendering of a source.list from template for ubuntu @@ -134,7 +131,6 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): 'http://archive.ubuntu.com/ubuntu/'], 'http://archive.ubuntu.com/ubuntu/') - def test_apt_srcl_custom(self): """ test_apt_srcl_custom Test rendering from a custom source.list template @@ -143,10 +139,10 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): mycloud = self._get_cloud('ubuntu') # the second mock restores the original subp - with mock.patch.object(util, 'write_file') as mockwrite, \ - mock.patch.object(util, 'subp', self.subp) as mocksubp: - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) + with mock.patch.object(util, 'write_file') as mockwrite: + with mock.patch.object(util, 'subp', self.subp) as mocksubp: + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) mockwrite.assert_called_once_with( '/etc/apt/sources.list', -- cgit v1.2.3 From 4f6b14ea0f21f015ce73a28d985ecded981b931d Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 16:54:20 +0200 Subject: remove no more applicable "not supported" statements --- tests/unittests/test_handler/test_handler_apt_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 38c93e0e..23f458eb 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -141,7 +141,7 @@ class TestAptSourceConfig(TestCase): def test_apt_source_keyonly(self): """ test_apt_source_keyonly - Test specification key without source (not yet supported) + Test specification key without source """ params = self._get_default_params() cfg = {'key': "fakekey 4242", @@ -158,7 +158,7 @@ class TestAptSourceConfig(TestCase): def test_apt_source_keyidonly(self): """ test_apt_source_keyidonly - Test specification of a keyid without source (not yet supported) + Test specification of a keyid without source """ params = self._get_default_params() cfg = {'keyid': "03683F77", -- cgit v1.2.3 From 46f930a718a89c514ba7caa22096201e7c6ca0a8 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:00:30 +0200 Subject: Adding test_apt_source_keyid_real and test_apt_source_longkeyid_real This now ensures that the stack of fetching IDs from keyservers and adding them really works by comparing against known good keys that are expected. --- .../test_handler/test_handler_apt_source.py | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 23f458eb..e50c7468 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -17,6 +17,19 @@ from cloudinit.config import cc_apt_configure from ..helpers import TestCase +EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ +NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy +8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0 +HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG +CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29 +OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ +FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm +S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== +=ACB2 +-----END PGP PUBLIC KEY BLOCK-----""" def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url @@ -173,6 +186,42 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) + def test_apt_source_keyid_real(self): + """ test_apt_source_keyid_real + Test specification of a keyid without source incl + up to addition of the key (nothing but add_key_raw mocked) + """ + keyid = "03683F77" + params = self._get_default_params() + cfg = {'keyid': keyid, + 'filename': self.aptlistfile} + + with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: + cc_apt_configure.add_sources([cfg], params) + + mockobj.assert_called_with(EXPECTEDKEY) + + # filename should be ignored on key only + self.assertFalse(os.path.isfile(self.aptlistfile)) + + def test_apt_source_longkeyid_real(self): + """ test_apt_source_keyid_real + Test specification of a long key fingerprint without source incl + up to addition of the key (nothing but add_key_raw mocked) + """ + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" + params = self._get_default_params() + cfg = {'keyid': keyid, + 'filename': self.aptlistfile} + + with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: + cc_apt_configure.add_sources([cfg], params) + + mockobj.assert_called_with(EXPECTEDKEY) + + # filename should be ignored on key only + self.assertFalse(os.path.isfile(self.aptlistfile)) + def test_apt_source_ppa(self): """ test_apt_source_ppa Test specification of a ppa -- cgit v1.2.3 From 9c098751b8065da609566572a7badd0d66c2b6ba Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:01:30 +0200 Subject: remove superfluous import --- tests/unittests/test_handler/test_handler_apt_source.py | 1 - 1 file changed, 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index e50c7468..88b4ccc6 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -11,7 +11,6 @@ try: except ImportError: import mock -from cloudinit import distros from cloudinit import util from cloudinit.config import cc_apt_configure -- cgit v1.2.3 From 3ece03a2df95bdb40851b9d629b39c43a233868b Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:03:51 +0200 Subject: alphabetical import order --- doc/examples/cloud-config.txt | 3 +++ tests/unittests/test_handler/test_handler_apt_source.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt index 75a4b6d4..8adc5a96 100644 --- a/doc/examples/cloud-config.txt +++ b/doc/examples/cloud-config.txt @@ -144,6 +144,9 @@ apt_sources: # this would only import the key without adding a ppa or other source spec - keyid: F430BBA5 # GPG key ID published on a key server + # In general keyid's can also be specified via their long fingerprints + - keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77 + # Custom apt repository: # * The apt signing key can also be specified # by providing a pgp public key block diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 88b4ccc6..439bd038 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -2,17 +2,17 @@ Testing various config variations of the apt_source config """ import os +import re import shutil import tempfile -import re try: from unittest import mock except ImportError: import mock -from cloudinit import util from cloudinit.config import cc_apt_configure +from cloudinit import util from ..helpers import TestCase -- cgit v1.2.3 From 338198c5a802207cabd7c13422fcf0e03c667ce7 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:04:34 +0200 Subject: alphabetical order on imports --- .../test_handler/test_handler_apt_configure_sources_list.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 16d6a5d1..d4fdee21 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -1,12 +1,11 @@ """ test_handler_apt_configure_sources_list Test templating of sources list """ +import logging import os +import re import shutil import tempfile -import re - -import logging try: from unittest import mock @@ -15,12 +14,12 @@ except ImportError: from cloudinit import cloud from cloudinit import distros -from cloudinit import util from cloudinit import helpers from cloudinit import templater +from cloudinit import util -from cloudinit.sources import DataSourceNone from cloudinit.config import cc_apt_configure +from cloudinit.sources import DataSourceNone from .. import helpers as t_help -- cgit v1.2.3 From 2a54898f8009f1511862a75467e84a77bef1943a Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:13:57 +0200 Subject: improve spacing in apt_source_list test --- .../test_handler_apt_configure_sources_list.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index d4fdee21..3bcd93cf 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -91,16 +91,12 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): cc_apt_configure.handle("notimportant", cfg, mycloud, LOG, None) - mockisfile.assert_any_call(('/etc/cloud/templates/' - 'sources.list.%s.tmpl' % distro)) - mocktmpl.assert_called_once_with(('/etc/cloud/templates/' - 'sources.list.%s.tmpl' % distro), - '/etc/apt/sources.list', - {'codename': '', - 'primary': - mirrorcheck, - 'mirror': - mirrorcheck}) + mockisfile.assert_any_call( + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) + mocktmpl.assert_called_once_with( + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro), + '/etc/apt/sources.list', + {'codename': '', 'primary': mirrorcheck, 'mirror': mirrorcheck}) def test_apt_source_list_debian(self): """ test_apt_source_list_debian -- cgit v1.2.3 From 0a407b63f61f48d310d205f833c1a2792273ec80 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:20:29 +0200 Subject: streamline code and sanitize expected result string definition --- .../test_handler_apt_configure_sources_list.py | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 3bcd93cf..1aa4f553 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -44,6 +44,22 @@ apt_custom_sources_list: | # FIND_SOMETHING_SPECIAL """ +EXPECTED_CONVERTED_CONTENT = ( + """## Note, this file is written by cloud-init on first boot of an instance +## modifications made here will not survive a re-bundle. +## if you wish to make changes you can: +## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg +## or do the same in user-data +## b.) add sources in /etc/apt/sources.list.d +## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl + +# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to +# newer versions of the distribution. +deb http://archive.ubuntu.com/ubuntu/ xenial main restricted +deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted +# FIND_SOMETHING_SPECIAL +""") + def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url @@ -141,17 +157,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): mockwrite.assert_called_once_with( '/etc/apt/sources.list', - ("## Note, this file is written by cloud-init on first boot of an" - " instance\n## modifications made here will not survive a re-bun" - "dle.\n## if you wish to make changes you can:\n## a.) add 'apt_" - "preserve_sources_list: true' to /etc/cloud/cloud.cfg\n## or" - " do the same in user-data\n## b.) add sources in /etc/apt/sourc" - "es.list.d\n## c.) make changes to template file /etc/cloud/temp" - "lates/sources.list.tmpl\n\n# See http://help.ubuntu.com/communi" - "ty/UpgradeNotes for how to upgrade to\n# newer versions of the " - "distribution.\ndeb http://archive.ubuntu.com/ubuntu/ xenial mai" - "n restricted\ndeb-src http://archive.ubuntu.com/ubuntu/ xenial " - "main restricted\n# FIND_SOMETHING_SPECIAL\n"), + EXPECTED_CONVERTED_CONTENT, mode=420) -- cgit v1.2.3 From 454de24c7d457b980c91849b128efe4faee62032 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 12 May 2016 21:21:36 +0200 Subject: make pep8 happy with a few spaces --- cloudinit/config/cc_apt_configure.py | 2 ++ tests/unittests/test_handler/test_handler_apt_source.py | 1 + 2 files changed, 3 insertions(+) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index e7b8a9b3..e5a962ac 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -171,6 +171,7 @@ def generate_sources_list(cfg, codename, mirrors, cloud, log): templater.render_to_file(template_fn, '/etc/apt/sources.list', params) + def add_key_raw(key): """ actual adding of a key as defined in key argument @@ -181,6 +182,7 @@ def add_key_raw(key): except: raise Exception('failed add key') + def add_key(ent): """ add key to the system as defiend in ent (if any) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 439bd038..e130392c 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -30,6 +30,7 @@ S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== =ACB2 -----END PGP PUBLIC KEY BLOCK-----""" + def load_tfile_or_url(*args, **kwargs): """ load_tfile_or_url load file and return content after decoding -- cgit v1.2.3 From 880d9fc2f9c62abf19b1506595aa81e5417dea45 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 19 May 2016 14:13:07 -0700 Subject: Adjust net module to be more isolated This allows it to be used outside of cloudinit more easily in the future. --- cloudinit/net/__init__.py | 108 ++++++++++++++++++--------------- cloudinit/net/cmdline.py | 15 ++++- cloudinit/net/eni.py | 35 +++++------ cloudinit/net/network_state.py | 38 +++++++++--- cloudinit/sources/helpers/openstack.py | 4 ++ cloudinit/util.py | 9 --- tests/unittests/test_net.py | 7 ++- 7 files changed, 129 insertions(+), 87 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 0202cbd8..07e7307e 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -17,44 +17,81 @@ # along with Curtin. If not, see . import errno +import logging import os - -from cloudinit import log as logging -from cloudinit.net import network_state -from cloudinit import util - +import six +import yaml LOG = logging.getLogger(__name__) SYS_CLASS_NET = "/sys/class/net/" LINKS_FNAME_PREFIX = "etc/systemd/network/50-cloud-init-" DEFAULT_PRIMARY_INTERFACE = 'eth0' +# NOTE(harlowja): some of these are similar to what is in cloudinit main +# source or utils tree/module but the reason that is done is so that this +# whole module can be easily extracted and placed into other +# code-bases (curtin for example). -def sys_dev_path(devname, path=""): - return SYS_CLASS_NET + devname + "/" + path +def write_file(path, content): + """Simple writing a file helper.""" + base_path = os.path.dirname(path) + if not os.path.isdir(base_path): + os.makedirs(base_path) + with open(path, "wb+") as fh: + if isinstance(content, six.text_type): + content = content.encode("utf8") + fh.write(content) -def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None): +def read_file(path, decode='utf8', enoent=None): try: - contents = "" - with open(sys_dev_path(devname, path), "r") as fp: - contents = fp.read().strip() - if translate is None: - return contents - - try: - return translate.get(contents) - except KeyError: - LOG.debug("found unexpected value '%s' in '%s/%s'", contents, - devname, path) - if keyerror is not None: - return keyerror - raise + with open(path, "rb") as fh: + contents = fh.load() except OSError as e: if e.errno == errno.ENOENT and enoent is not None: return enoent raise + if decode: + return contents.decode(decode) + return contents + + +def dump_yaml(obj): + return yaml.safe_dump(obj, + line_break="\n", + indent=4, + explicit_start=True, + explicit_end=True, + default_flow_style=False) + + +def read_yaml_file(path): + val = yaml.safe_load(read_file(path)) + if not isinstance(val, dict): + gotten_type_name = type(val).__name__ + raise TypeError("Expected dict to be loaded from %s, got" + " '%s' instead" % (path, gotten_type_name)) + return val + + +def sys_dev_path(devname, path=""): + return SYS_CLASS_NET + devname + "/" + path + + +def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None): + contents = read_file(sys_dev_path(devname, path), enoent=enoent) + contents = contents.strip() + if translate is None: + return contents + try: + return translate.get(contents) + except KeyError: + LOG.debug("found unexpected value '%s' in '%s/%s'", contents, + devname, path) + if keyerror is not None: + return keyerror + raise def is_up(devname): @@ -107,31 +144,6 @@ class ParserError(Exception): """Raised when parser has issue parsing the interfaces file.""" -def parse_net_config_data(net_config, skip_broken=True): - """Parses the config, returns NetworkState object - - :param net_config: curtin network config dict - """ - state = None - if 'version' in net_config and 'config' in net_config: - ns = network_state.NetworkState(version=net_config.get('version'), - config=net_config.get('config')) - ns.parse_config(skip_broken=skip_broken) - state = ns.network_state - return state - - -def parse_net_config(path, skip_broken=True): - """Parses a curtin network configuration file and - return network state""" - ns = None - net_config = util.read_conf(path) - if 'network' in net_config: - ns = parse_net_config_data(net_config.get('network'), - skip_broken=skip_broken) - return ns - - def is_disabled_cfg(cfg): if not cfg or not isinstance(cfg, dict): return False @@ -146,7 +158,7 @@ def sys_netdev_info(name, field): fname = os.path.join(SYS_CLASS_NET, name, field) if not os.path.exists(fname): raise OSError("%s: could not find sysfs entry: %s" % (name, fname)) - data = util.load_file(fname) + data = read_file(fname) if data[-1] == '\n': data = data[:-1] return data diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py index f5712533..b85d4b0a 100644 --- a/cloudinit/net/cmdline.py +++ b/cloudinit/net/cmdline.py @@ -20,12 +20,25 @@ import base64 import glob import gzip import io +import shlex +import sys + +import six from cloudinit.net import get_devicelist from cloudinit.net import sys_netdev_info from cloudinit import util +PY26 = sys.version_info[0:2] == (2, 6) + + +def _shlex_split(blob): + if PY26 and isinstance(blob, six.text_type): + # Older versions don't support unicode input + blob = blob.encode("utf8") + return shlex.split(blob) + def _load_shell_content(content, add_empty=False, empty_val=None): """Given shell like syntax (key=value\nkey2=value2\n) in content @@ -33,7 +46,7 @@ def _load_shell_content(content, add_empty=False, empty_val=None): then add entries in to the returned dictionary for 'VAR=' variables. Set their value to empty_val.""" data = {} - for line in util.shlex_split(content): + for line in _shlex_split(content): try: key, value = line.split("=", 1) except ValueError: diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index b1bdac24..adb31c22 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -16,10 +16,11 @@ import glob import os import re +from cloudinit import net + from cloudinit.net import LINKS_FNAME_PREFIX from cloudinit.net import ParserError from cloudinit.net.udev import generate_udev_rule -from cloudinit import util NET_CONFIG_COMMANDS = [ @@ -363,16 +364,13 @@ class Renderer(object): links_prefix=LINKS_FNAME_PREFIX, netrules='etc/udev/rules.d/70-persistent-net.rules'): - fpeni = os.path.sep.join((target, eni,)) - util.ensure_dir(os.path.dirname(fpeni)) - with open(fpeni, 'w+') as f: - f.write(self._render_interfaces(network_state)) + fpeni = os.path.join(target, eni) + net.write_file(fpeni, self._render_interfaces(network_state)) if netrules: - netrules = os.path.sep.join((target, netrules,)) - util.ensure_dir(os.path.dirname(netrules)) - with open(netrules, 'w+') as f: - f.write(self._render_persistent_net(network_state)) + netrules = os.path.join(target, netrules) + net.write_file(netrules, + self._render_persistent_net(network_state)) if links_prefix: self._render_systemd_links(target, network_state, links_prefix) @@ -382,18 +380,17 @@ class Renderer(object): fp_prefix = os.path.sep.join((target, links_prefix)) for f in glob.glob(fp_prefix + "*"): os.unlink(f) - interfaces = network_state.get('interfaces') for iface in interfaces.values(): if (iface['type'] == 'physical' and 'name' in iface and iface.get('mac_address')): fname = fp_prefix + iface['name'] + ".link" - with open(fname, "w") as fp: - fp.write("\n".join([ - "[Match]", - "MACAddress=" + iface['mac_address'], - "", - "[Link]", - "Name=" + iface['name'], - "" - ])) + content = "\n".join([ + "[Match]", + "MACAddress=" + iface['mac_address'], + "", + "[Link]", + "Name=" + iface['name'], + "" + ]) + net.write_file(fname, content) diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index 2feffa71..c5aeadb5 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -16,12 +16,11 @@ # along with Curtin. If not, see . import copy +import logging import six -from cloudinit import log as logging -from cloudinit import util -from cloudinit.util import yaml_dumps as dump_config +from cloudinit import net LOG = logging.getLogger(__name__) @@ -31,9 +30,34 @@ NETWORK_STATE_REQUIRED_KEYS = { } +def parse_net_config_data(net_config, skip_broken=True): + """Parses the config, returns NetworkState object + + :param net_config: curtin network config dict + """ + state = None + if 'version' in net_config and 'config' in net_config: + ns = NetworkState(version=net_config.get('version'), + config=net_config.get('config')) + ns.parse_config(skip_broken=skip_broken) + state = ns.network_state + return state + + +def parse_net_config(path, skip_broken=True): + """Parses a curtin network configuration file and + return network state""" + ns = None + net_config = net.read_yaml_file(path) + if 'network' in net_config: + ns = parse_net_config_data(net_config.get('network'), + skip_broken=skip_broken) + return ns + + def from_state_file(state_file): network_state = None - state = util.read_conf(state_file) + state = net.read_yaml_file(state_file) network_state = NetworkState() network_state.load(state) return network_state @@ -111,7 +135,7 @@ class NetworkState(object): 'config': self.config, 'network_state': self.network_state, } - return dump_config(state) + return net.dump_yaml(state) def load(self, state): if 'version' not in state: @@ -121,7 +145,7 @@ class NetworkState(object): required_keys = NETWORK_STATE_REQUIRED_KEYS[state['version']] missing_keys = diff_keys(required_keys, state) if missing_keys: - msg = 'Invalid state, missing keys: %s'.format(missing_keys) + msg = 'Invalid state, missing keys: %s' % (missing_keys) LOG.error(msg) raise ValueError(msg) @@ -130,7 +154,7 @@ class NetworkState(object): setattr(self, key, state[key]) def dump_network_state(self): - return dump_config(self.network_state) + return net.dump_yaml(self.network_state) def parse_config(self, skip_broken=True): # rebuild network state diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py index b2acc648..f85fd864 100644 --- a/cloudinit/sources/helpers/openstack.py +++ b/cloudinit/sources/helpers/openstack.py @@ -551,6 +551,10 @@ def convert_net_json(network_json): 'type': 'static', 'address': network.get('ip_address'), }) + if network['type'] == 'ipv6': + subnet['ipv6'] = True + else: + subnet['ipv4'] = True subnets.append(subnet) cfg.update({'subnets': subnets}) if link['type'] in ['ethernet', 'vif', 'ovs', 'phy']: diff --git a/cloudinit/util.py b/cloudinit/util.py index 7562b97a..2bec476e 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -37,7 +37,6 @@ import pwd import random import re import shutil -import shlex import socket import stat import string @@ -82,7 +81,6 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], ['lxc-is-container']) PROC_CMDLINE = None -PY26 = sys.version_info[0:2] == (2, 6) def decode_binary(blob, encoding='utf-8'): @@ -1202,13 +1200,6 @@ def load_file(fname, read_cb=None, quiet=False, decode=True): return contents -def shlex_split(blob): - if PY26 and isinstance(blob, six.text_type): - # Older versions don't support unicode input - blob = blob.encode("utf8") - return shlex.split(blob) - - def get_cmdline(): if 'DEBUG_PROC_CMDLINE' in os.environ: return os.environ["DEBUG_PROC_CMDLINE"] diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index ed2c6d0f..75c433f6 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -2,6 +2,7 @@ from cloudinit import util from cloudinit import net from cloudinit.net import cmdline from cloudinit.net import eni +from cloudinit.net import network_state from .helpers import TestCase from .helpers import mock @@ -112,14 +113,14 @@ class TestEniNetRendering(TestCase): mock_sys_dev_path.side_effect = sys_dev_path network_cfg = net.generate_fallback_config() - network_state = net.parse_net_config_data(network_cfg, - skip_broken=False) + ns = network_state.parse_net_config_data(network_cfg, + skip_broken=False) render_dir = os.path.join(tmp_dir, "render") os.makedirs(render_dir) renderer = eni.Renderer() - renderer.render_network_state(render_dir, network_state, + renderer.render_network_state(render_dir, ns, eni="interfaces", links_prefix=None, netrules=None) -- cgit v1.2.3 From 2fb5af62229b8975910bf0ef63731047bd8d7e63 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 19 May 2016 15:33:15 -0700 Subject: Fix up tests and flake8 warnings --- cloudinit/net/__init__.py | 2 +- cloudinit/net/eni.py | 16 ++++----- cloudinit/sources/DataSourceConfigDrive.py | 1 - cloudinit/stages.py | 2 +- tests/unittests/helpers.py | 1 + tests/unittests/test__init__.py | 1 - tests/unittests/test_datasource/test_cloudsigma.py | 1 - .../unittests/test_datasource/test_configdrive.py | 41 ++++++++++++---------- tests/unittests/test_datasource/test_nocloud.py | 2 ++ tests/unittests/test_datasource/test_smartos.py | 2 +- tests/unittests/test_net.py | 6 ++-- tests/unittests/test_rh_subscription.py | 5 +-- tox.ini | 4 +-- 13 files changed, 41 insertions(+), 43 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index ad44911b..ba0e39ae 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -49,8 +49,8 @@ DEFAULT_PRIMARY_INTERFACE = 'eth0' # whole module can be easily extracted and placed into other # code-bases (curtin for example). + def write_file(path, content): - """Simple writing a file helper.""" base_path = os.path.dirname(path) if not os.path.isdir(base_path): os.makedirs(base_path) diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index adb31c22..18bae97a 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -258,7 +258,7 @@ class Renderer(object): return content def _render_route(self, route, indent=""): - """ When rendering routes for an iface, in some cases applying a route + """When rendering routes for an iface, in some cases applying a route may result in the route command returning non-zero which produces some confusing output for users manually using ifup/ifdown[1]. To that end, we will optionally include an '|| true' postfix to each @@ -302,7 +302,7 @@ class Renderer(object): return content def _render_interfaces(self, network_state): - ''' Given state, emit etc/network/interfaces content ''' + '''Given state, emit etc/network/interfaces content''' content = "" interfaces = network_state.get('interfaces') @@ -336,8 +336,8 @@ class Renderer(object): iface['control'] = subnet.get('control', 'auto') if iface['mode'].endswith('6'): iface['inet'] += '6' - elif iface['mode'] == 'static' \ - and ":" in subnet['address']: + elif (iface['mode'] == 'static' + and ":" in subnet['address']): iface['inet'] += '6' if iface['mode'].startswith('dhcp'): iface['mode'] = 'dhcp' @@ -359,10 +359,10 @@ class Renderer(object): content = content.replace('mac_address', 'hwaddress') return content - def render_network_state(self, - target, network_state, eni="etc/network/interfaces", - links_prefix=LINKS_FNAME_PREFIX, - netrules='etc/udev/rules.d/70-persistent-net.rules'): + def render_network_state( + self, target, network_state, + eni="etc/network/interfaces", links_prefix=LINKS_FNAME_PREFIX, + netrules='etc/udev/rules.d/70-persistent-net.rules'): fpeni = os.path.join(target, eni) net.write_file(fpeni, self._render_interfaces(network_state)) diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py index 70373b43..4478c4e2 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -18,7 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import copy import os from cloudinit import log as logging diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 5dd31539..b837009a 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -44,8 +44,8 @@ from cloudinit import helpers from cloudinit import importer from cloudinit import log as logging from cloudinit import net -from cloudinit.reporting import events from cloudinit.net import cmdline +from cloudinit.reporting import events from cloudinit import sources from cloudinit import type_utils from cloudinit import util diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 7b4d44e8..8d46a8bf 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -45,6 +45,7 @@ else: if _PY_MINOR == 4 and _PY_MICRO < 3: FIX_HTTPRETTY = True + # Makes the old path start # with new base instead of whatever # it previously had diff --git a/tests/unittests/test__init__.py b/tests/unittests/test__init__.py index a9b35afe..0154784a 100644 --- a/tests/unittests/test__init__.py +++ b/tests/unittests/test__init__.py @@ -1,7 +1,6 @@ import os import shutil import tempfile -import unittest2 from cloudinit import handlers from cloudinit import helpers diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py index 7950fc52..2a42ce0c 100644 --- a/tests/unittests/test_datasource/test_cloudsigma.py +++ b/tests/unittests/test_datasource/test_cloudsigma.py @@ -6,7 +6,6 @@ from cloudinit.cs_utils import Cepko from cloudinit.sources import DataSourceCloudSigma from .. import helpers as test_helpers -from ..helpers import SkipTest SERVER_CONTEXT = { "cpu": 1000, diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index 1db50798..5395e544 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -368,30 +368,32 @@ class TestNetJson(TestCase): self.assertEqual(myds.network_config, network_config) def test_network_config_conversions(self): - """Tests a bunch of input network json and checks the expected conversions.""" + """Tests a bunch of input network json and checks the + expected conversions.""" in_datas = [ NETWORK_DATA, { 'services': [{'type': 'dns', 'address': '172.19.0.12'}], - 'networks': [ - {'network_id': 'dacd568d-5be6-4786-91fe-750c374b78b4', - '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', + 'networks': [{ + 'network_id': 'dacd568d-5be6-4786-91fe-750c374b78b4', + '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', + }], + 'links': [{ + 'type': 'bridge', + 'vif_id': '1a81968a-797a-400f-8a80-567f997eb93f', + 'ethernet_mac_address': 'fa:16:3e:ed:9a:59', + 'id': 'tap1a81968a-79', + 'mtu': None, }], - 'links': [ - {'type': 'bridge', - 'vif_id': '1a81968a-797a-400f-8a80-567f997eb93f', - 'ethernet_mac_address': 'fa:16:3e:ed:9a:59', - 'id': 'tap1a81968a-79', 'mtu': None}] }, ] out_datas = [ @@ -440,6 +442,7 @@ class TestNetJson(TestCase): 'address': '172.19.1.34', 'netmask': '255.255.252.0', 'type': 'static', + 'ipv4': True, 'routes': [{ 'gateway': '172.19.3.254', 'netmask': '0.0.0.0', diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py index 077603b4..b0fa1130 100644 --- a/tests/unittests/test_datasource/test_nocloud.py +++ b/tests/unittests/test_datasource/test_nocloud.py @@ -7,6 +7,8 @@ import os import shutil import tempfile +import yaml + class TestNoCloudDataSource(TestCase): diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py index 2f159ac4..28f56039 100644 --- a/tests/unittests/test_datasource/test_smartos.py +++ b/tests/unittests/test_datasource/test_smartos.py @@ -42,7 +42,7 @@ from cloudinit import helpers as c_helpers from cloudinit.util import b64e from .. import helpers -from ..helpers import mock, SkipTest +from ..helpers import mock MOCK_RETURNS = { 'hostname': 'test-host', diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index faf0f0fb..7998111a 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -1,13 +1,11 @@ -from cloudinit import net -from cloudinit import util - from cloudinit import net from cloudinit.net import cmdline from cloudinit.net import eni from cloudinit.net import network_state +from cloudinit import util -from .helpers import TestCase from .helpers import mock +from .helpers import TestCase import base64 import copy diff --git a/tests/unittests/test_rh_subscription.py b/tests/unittests/test_rh_subscription.py index e4dcc58b..891dbe77 100644 --- a/tests/unittests/test_rh_subscription.py +++ b/tests/unittests/test_rh_subscription.py @@ -10,13 +10,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import logging -import mock -import unittest - from cloudinit.config import cc_rh_subscription +from cloudinit import util from .helpers import TestCase, mock diff --git a/tox.ini b/tox.ini index b92ebac1..7802a291 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] -envlist = py27,py3,flake8 +envlist = py26,py27,py3,flake8 recreate = True usedevelop = True [testenv] -commands = python -m nose {posargs:tests} +commands = nosetests {posargs:tests} deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt setenv = -- cgit v1.2.3 From 8575c4f7045c7074370bba73198ea36571224ece Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 08:21:59 +0200 Subject: generalize test_apt_source_basic to be reusable across more testcases --- .../test_handler/test_handler_apt_source.py | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index e130392c..1b294431 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -58,27 +58,34 @@ class TestAptSourceConfig(TestCase): params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" return params - def test_apt_source_basic(self): - """ test_apt_source_basic + def apt_source_basic(self, filename, cfg): + """ apt_source_basic Test Fix deb source string, has to overwrite mirror conf in params """ params = self._get_default_params() - cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted'), - 'filename': self.aptlistfile} cc_apt_configure.add_sources([cfg], params) - self.assertTrue(os.path.isfile(self.aptlistfile)) + self.assertTrue(os.path.isfile(filename)) - contents = load_tfile_or_url(self.aptlistfile) + contents = load_tfile_or_url(filename) self.assertTrue(re.search(r"%s %s %s %s\n" % ("deb", "http://archive.ubuntu.com/ubuntu", "karmic-backports", "main universe multiverse restricted"), contents, flags=re.IGNORECASE)) + def test_apt_source_basic(self): + """ test_apt_source_basic + Test Fix deb source string, has to overwrite mirror conf in params. + Test with a filename provided in config. + """ + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' + ' karmic-backports' + ' main universe multiverse restricted'), + 'filename': self.aptlistfile} + self.apt_source_basic(self.aptlistfile, cfg) + def test_apt_source_replacement(self): """ test_apt_source_replace Test Autoreplacement of MIRROR and RELEASE in source specs -- cgit v1.2.3 From 29065e4a52a747b4f4cf30092ddcc3744e6aa350 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 09:24:44 +0200 Subject: test_apt_source_basic_nofn check for non-specified filename Cloud-inint uses a default fallback, we want to ensure no code change modfies this behaviour. --- .../test_handler/test_handler_apt_source.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 1b294431..ce356fc6 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -47,6 +47,7 @@ class TestAptSourceConfig(TestCase): self.tmp = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmp) self.aptlistfile = os.path.join(self.tmp, "single-deb.list") + self.join = os.path.join @staticmethod def _get_default_params(): @@ -86,6 +87,30 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile} self.apt_source_basic(self.aptlistfile, cfg) + def test_apt_source_basic_nofn(self): + """ test_apt_source_basic_nofn + Test Fix deb source string, has to overwrite mirror conf in params. + Test without a filename provided in config and test for known fallback. + """ + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' + ' karmic-backports' + ' main universe multiverse restricted')} + # mock into writable tmp dir and check path/content there + filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", + "cloud_config_sources.list") + + def myjoin(*args, **kwargs): + """ myjoin - redir into writable tmpdir""" + if (args[0] == "/etc/apt/sources.list.d/" + and args[1] == "cloud_config_sources.list" + and len(args) == 2): + return self.join(self.tmp, args[0].lstrip("/"), args[1]) + else: + return self.join(*args, **kwargs) + + with mock.patch.object(os.path, 'join', side_effect=myjoin): + self.apt_source_basic(filename, cfg) + def test_apt_source_replacement(self): """ test_apt_source_replace Test Autoreplacement of MIRROR and RELEASE in source specs -- cgit v1.2.3 From f06dd57907caa648743a73566b2b6e62b96be2fb Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 09:34:23 +0200 Subject: drop unused mockappsubp --- tests/unittests/test_handler/test_handler_apt_configure_sources_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 1aa4f553..353422a2 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -151,7 +151,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): # the second mock restores the original subp with mock.patch.object(util, 'write_file') as mockwrite: - with mock.patch.object(util, 'subp', self.subp) as mocksubp: + with mock.patch.object(util, 'subp', self.subp): cc_apt_configure.handle("notimportant", cfg, mycloud, LOG, None) -- cgit v1.2.3 From cf37b78e9bada97a7cc223bd824fb0e8cd8c4c7c Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 10:48:03 +0200 Subject: extend test_apt_source_replace by a no-filename case --- .../test_handler/test_handler_apt_source.py | 53 +++++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index ce356fc6..050ee78f 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -59,6 +59,15 @@ class TestAptSourceConfig(TestCase): params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" return params + def myjoin(self, *args, **kwargs): + """ myjoin - redir into writable tmpdir""" + if (args[0] == "/etc/apt/sources.list.d/" + and args[1] == "cloud_config_sources.list" + and len(args) == 2): + return self.join(self.tmp, args[0].lstrip("/"), args[1]) + else: + return self.join(*args, **kwargs) + def apt_source_basic(self, filename, cfg): """ apt_source_basic Test Fix deb source string, has to overwrite mirror conf in params @@ -99,36 +108,46 @@ class TestAptSourceConfig(TestCase): filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") - def myjoin(*args, **kwargs): - """ myjoin - redir into writable tmpdir""" - if (args[0] == "/etc/apt/sources.list.d/" - and args[1] == "cloud_config_sources.list" - and len(args) == 2): - return self.join(self.tmp, args[0].lstrip("/"), args[1]) - else: - return self.join(*args, **kwargs) - - with mock.patch.object(os.path, 'join', side_effect=myjoin): + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): self.apt_source_basic(filename, cfg) - def test_apt_source_replacement(self): - """ test_apt_source_replace + def apt_source_replacement(self, filename, cfg): + """ apt_source_replace Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() - cfg = {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile} - cc_apt_configure.add_sources([cfg], params) - self.assertTrue(os.path.isfile(self.aptlistfile)) + self.assertTrue(os.path.isfile(filename)) - contents = load_tfile_or_url(self.aptlistfile) + contents = load_tfile_or_url(filename) self.assertTrue(re.search(r"%s %s %s %s\n" % ("deb", params['MIRROR'], params['RELEASE'], "multiverse"), contents, flags=re.IGNORECASE)) + def test_apt_source_replace(self): + """ test_apt_source_replace + Test Autoreplacement of MIRROR and RELEASE in source specs with + Filename being set + """ + cfg = {'source': 'deb $MIRROR $RELEASE multiverse', + 'filename': self.aptlistfile} + self.apt_source_replacement(self.aptlistfile, cfg) + + def test_apt_source_replace_nofn(self): + """ test_apt_source_replace_nofn + Test Autoreplacement of MIRROR and RELEASE in source specs with + No filename being set + """ + cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} + # mock into writable tmp dir and check path/content there + filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", + "cloud_config_sources.list") + + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + self.apt_source_replacement(filename, cfg) + def test_apt_source_keyid(self): """ test_apt_source_keyid Test specification of a source + keyid -- cgit v1.2.3 From 6b7711dbb75cd3fd31a638f3b530e863eb907708 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 10:53:16 +0200 Subject: extend test_apt_source_keyid by no filename case --- .../test_handler/test_handler_apt_source.py | 41 ++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 050ee78f..c4ba6d00 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -148,17 +148,11 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(os.path, 'join', side_effect=self.myjoin): self.apt_source_replacement(filename, cfg) - def test_apt_source_keyid(self): - """ test_apt_source_keyid + def apt_source_keyid(self, filename, cfg): + """ apt_source_keyid Test specification of a source + keyid """ params = self._get_default_params() - cfg = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'keyid': "03683F77", - 'filename': self.aptlistfile} with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: @@ -166,9 +160,9 @@ class TestAptSourceConfig(TestCase): mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1234') - self.assertTrue(os.path.isfile(self.aptlistfile)) + self.assertTrue(os.path.isfile(filename)) - contents = load_tfile_or_url(self.aptlistfile) + contents = load_tfile_or_url(filename) self.assertTrue(re.search(r"%s %s %s %s\n" % ("deb", ('http://ppa.launchpad.net/smoser/' @@ -176,6 +170,33 @@ class TestAptSourceConfig(TestCase): "xenial", "main"), contents, flags=re.IGNORECASE)) + def test_apt_source_keyid(self): + """ test_apt_source_keyid + Test specification of a source + keyid with filename being set + """ + cfg = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'keyid': "03683F77", + 'filename': self.aptlistfile} + self.apt_source_keyid(self.aptlistfile, cfg) + + def test_apt_source_keyid_nofn(self): + """ test_apt_source_keyid + Test specification of a source + keyid without filename being set + """ + cfg = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'keyid': "03683F77"} + # mock into writable tmp dir and check path/content there + filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", + "cloud_config_sources.list") + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + self.apt_source_keyid(filename, cfg) + def test_apt_source_key(self): """ test_apt_source_key Test specification of a source + key -- cgit v1.2.3 From 0b2cbda06a5622bc2b6dcc4f2d4190824528f1f6 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 10:55:37 +0200 Subject: put fallbackfn to init This was now used by multiple methods, no need to duplicate code. --- .../test_handler/test_handler_apt_source.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index c4ba6d00..79c33b4e 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -48,6 +48,10 @@ class TestAptSourceConfig(TestCase): self.addCleanup(shutil.rmtree, self.tmp) self.aptlistfile = os.path.join(self.tmp, "single-deb.list") self.join = os.path.join + # mock fallback filename into writable tmp dir + self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", + "cloud_config_sources.list") + @staticmethod def _get_default_params(): @@ -104,12 +108,8 @@ class TestAptSourceConfig(TestCase): cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' ' main universe multiverse restricted')} - # mock into writable tmp dir and check path/content there - filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", - "cloud_config_sources.list") - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_basic(filename, cfg) + self.apt_source_basic(self.fallbackfn, cfg) def apt_source_replacement(self, filename, cfg): """ apt_source_replace @@ -141,12 +141,8 @@ class TestAptSourceConfig(TestCase): No filename being set """ cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} - # mock into writable tmp dir and check path/content there - filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", - "cloud_config_sources.list") - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_replacement(filename, cfg) + self.apt_source_replacement(self.fallbackfn, cfg) def apt_source_keyid(self, filename, cfg): """ apt_source_keyid @@ -191,11 +187,8 @@ class TestAptSourceConfig(TestCase): 'smoser/cloud-init-test/ubuntu' ' xenial main'), 'keyid': "03683F77"} - # mock into writable tmp dir and check path/content there - filename = os.path.join(self.tmp, "etc/apt/sources.list.d/", - "cloud_config_sources.list") with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_keyid(filename, cfg) + self.apt_source_keyid(self.fallbackfn, cfg) def test_apt_source_key(self): """ test_apt_source_key -- cgit v1.2.3 From 1926eb2476f9e1fda3356c7828479231dccc309b Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 10:59:45 +0200 Subject: extend test_apt_source_key by nofn case --- .../test_handler/test_handler_apt_source.py | 38 ++++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 79c33b4e..516dc694 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -190,26 +190,20 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(os.path, 'join', side_effect=self.myjoin): self.apt_source_keyid(self.fallbackfn, cfg) - def test_apt_source_key(self): - """ test_apt_source_key + def apt_source_key(self, filename, cfg): + """ apt_source_key Test specification of a source + key """ params = self._get_default_params() - cfg = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'key': "fakekey 4321", - 'filename': self.aptlistfile} with mock.patch.object(util, 'subp') as mockobj: cc_apt_configure.add_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') - self.assertTrue(os.path.isfile(self.aptlistfile)) + self.assertTrue(os.path.isfile(filename)) - contents = load_tfile_or_url(self.aptlistfile) + contents = load_tfile_or_url(filename) self.assertTrue(re.search(r"%s %s %s %s\n" % ("deb", ('http://ppa.launchpad.net/smoser/' @@ -217,6 +211,30 @@ class TestAptSourceConfig(TestCase): "xenial", "main"), contents, flags=re.IGNORECASE)) + def test_apt_source_key(self): + """ test_apt_source_key + Test specification of a source + key with filename being set + """ + cfg = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'key': "fakekey 4321", + 'filename': self.aptlistfile} + self.apt_source_key(self.aptlistfile, cfg) + + def test_apt_source_key_nofn(self): + """ test_apt_source_key + Test specification of a source + key without filename being set + """ + cfg = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'key': "fakekey 4321"} + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + self.apt_source_key(self.fallbackfn, cfg) + def test_apt_source_keyonly(self): """ test_apt_source_keyonly Test specification key without source -- cgit v1.2.3 From 3560afc50569169ceddf95ff755d231fd5858143 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 13:34:27 +0200 Subject: fix function names in inline doc --- tests/unittests/test_handler/test_handler_apt_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 516dc694..6441374d 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -179,7 +179,7 @@ class TestAptSourceConfig(TestCase): self.apt_source_keyid(self.aptlistfile, cfg) def test_apt_source_keyid_nofn(self): - """ test_apt_source_keyid + """ test_apt_source_keyid_nofn Test specification of a source + keyid without filename being set """ cfg = {'source': ('deb ' @@ -224,7 +224,7 @@ class TestAptSourceConfig(TestCase): self.apt_source_key(self.aptlistfile, cfg) def test_apt_source_key_nofn(self): - """ test_apt_source_key + """ test_apt_source_key_nofn Test specification of a source + key without filename being set """ cfg = {'source': ('deb ' -- cgit v1.2.3 From db5188ca3b9eac1097735ce42807881266d29543 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 13:57:17 +0200 Subject: testcases with multiple source list entries --- .../test_handler/test_handler_apt_source.py | 74 ++++++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 6441374d..dcbd51e6 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -47,6 +47,8 @@ class TestAptSourceConfig(TestCase): self.tmp = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmp) self.aptlistfile = os.path.join(self.tmp, "single-deb.list") + self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") + self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") self.join = os.path.join # mock fallback filename into writable tmp dir self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", @@ -78,7 +80,7 @@ class TestAptSourceConfig(TestCase): """ params = self._get_default_params() - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_sources(cfg, params) self.assertTrue(os.path.isfile(filename)) @@ -98,7 +100,40 @@ class TestAptSourceConfig(TestCase): ' karmic-backports' ' main universe multiverse restricted'), 'filename': self.aptlistfile} - self.apt_source_basic(self.aptlistfile, cfg) + self.apt_source_basic(self.aptlistfile, [cfg]) + + def test_apt_source_basic_triple(self): + """ test_apt_source_basic_triple + Test Fix three deb source string, has to overwrite mirror conf in + params. Test with filenames provided in config. + """ + cfg1 = {'source': ('deb http://archive.ubuntu.com/ubuntu' + ' karmic-backports' + ' main universe multiverse restricted'), + 'filename': self.aptlistfile} + cfg2 = {'source': ('deb http://archive.ubuntu.com/ubuntu' + ' precise-backports' + ' main universe multiverse restricted'), + 'filename': self.aptlistfile2} + cfg3 = {'source': ('deb http://archive.ubuntu.com/ubuntu' + ' lucid-backports' + ' main universe multiverse restricted'), + 'filename': self.aptlistfile3} + self.apt_source_basic(self.aptlistfile, [cfg1, cfg2, cfg3]) + + # extra verify on two extra files of this test + contents = load_tfile_or_url(self.aptlistfile2) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", "http://archive.ubuntu.com/ubuntu", + "precise-backports", + "main universe multiverse restricted"), + contents, flags=re.IGNORECASE)) + contents = load_tfile_or_url(self.aptlistfile3) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", "http://archive.ubuntu.com/ubuntu", + "lucid-backports", + "main universe multiverse restricted"), + contents, flags=re.IGNORECASE)) def test_apt_source_basic_nofn(self): """ test_apt_source_basic_nofn @@ -109,14 +144,14 @@ class TestAptSourceConfig(TestCase): ' karmic-backports' ' main universe multiverse restricted')} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_basic(self.fallbackfn, cfg) + self.apt_source_basic(self.fallbackfn, [cfg]) def apt_source_replacement(self, filename, cfg): """ apt_source_replace Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_sources(cfg, params) self.assertTrue(os.path.isfile(filename)) @@ -133,7 +168,34 @@ class TestAptSourceConfig(TestCase): """ cfg = {'source': 'deb $MIRROR $RELEASE multiverse', 'filename': self.aptlistfile} - self.apt_source_replacement(self.aptlistfile, cfg) + self.apt_source_replacement(self.aptlistfile, [cfg]) + + def test_apt_source_replace_triple(self): + """ test_apt_source_replace_triple + Test three autoreplacements of MIRROR and RELEASE in source specs with + Filename being set + """ + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', + 'filename': self.aptlistfile} + cfg2 = {'source': 'deb $MIRROR $RELEASE main', + 'filename': self.aptlistfile2} + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', + 'filename': self.aptlistfile3} + self.apt_source_replacement(self.aptlistfile, [cfg1, cfg2, cfg3]) + + # extra verify on two extra files of this test + params = self._get_default_params() + contents = load_tfile_or_url(self.aptlistfile2) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", params['MIRROR'], params['RELEASE'], + "main"), + contents, flags=re.IGNORECASE)) + contents = load_tfile_or_url(self.aptlistfile3) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", params['MIRROR'], params['RELEASE'], + "universe"), + contents, flags=re.IGNORECASE)) + def test_apt_source_replace_nofn(self): """ test_apt_source_replace_nofn @@ -142,7 +204,7 @@ class TestAptSourceConfig(TestCase): """ cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_replacement(self.fallbackfn, cfg) + self.apt_source_replacement(self.fallbackfn, [cfg]) def apt_source_keyid(self, filename, cfg): """ apt_source_keyid -- cgit v1.2.3 From a62d3ddcf6ec06ccf0f3c2dd0fd969c83304d89b Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 14:13:18 +0200 Subject: add triple case for test_apt_source_keyid_triple incl triple key check --- .../test_handler/test_handler_apt_source.py | 55 ++++++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index dcbd51e6..9edd3df5 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -10,6 +10,7 @@ try: from unittest import mock except ImportError: import mock +from mock import call from cloudinit.config import cc_apt_configure from cloudinit import util @@ -206,7 +207,7 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(os.path, 'join', side_effect=self.myjoin): self.apt_source_replacement(self.fallbackfn, [cfg]) - def apt_source_keyid(self, filename, cfg): + def apt_source_keyid(self, filename, cfg, keynum): """ apt_source_keyid Test specification of a source + keyid """ @@ -214,9 +215,13 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_sources(cfg, params) - mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1234') + # check if it added the right ammount of keys + calls = [] + for i in range(keynum): + calls.append(call(('apt-key', 'add', '-'), 'fakekey 1234')) + mockobj.assert_has_calls(calls, any_order=True) self.assertTrue(os.path.isfile(filename)) @@ -238,7 +243,47 @@ class TestAptSourceConfig(TestCase): ' xenial main'), 'keyid': "03683F77", 'filename': self.aptlistfile} - self.apt_source_keyid(self.aptlistfile, cfg) + self.apt_source_keyid(self.aptlistfile, [cfg], 1) + + def test_apt_source_keyid_triple(self): + """ test_apt_source_keyid_triple + Test specification of a source + keyid with filename being set + Setting three of such, check for content and keys + """ + cfg1 = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial main'), + 'keyid': "03683F77", + 'filename': self.aptlistfile} + cfg2 = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial universe'), + 'keyid': "03683F77", + 'filename': self.aptlistfile2} + cfg3 = {'source': ('deb ' + 'http://ppa.launchpad.net/' + 'smoser/cloud-init-test/ubuntu' + ' xenial multiverse'), + 'keyid': "03683F77", + 'filename': self.aptlistfile3} + + self.apt_source_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) + contents = load_tfile_or_url(self.aptlistfile2) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", + ('http://ppa.launchpad.net/smoser/' + 'cloud-init-test/ubuntu'), + "xenial", "universe"), + contents, flags=re.IGNORECASE)) + contents = load_tfile_or_url(self.aptlistfile3) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", + ('http://ppa.launchpad.net/smoser/' + 'cloud-init-test/ubuntu'), + "xenial", "multiverse"), + contents, flags=re.IGNORECASE)) def test_apt_source_keyid_nofn(self): """ test_apt_source_keyid_nofn @@ -250,7 +295,7 @@ class TestAptSourceConfig(TestCase): ' xenial main'), 'keyid': "03683F77"} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_keyid(self.fallbackfn, cfg) + self.apt_source_keyid(self.fallbackfn, [cfg], 1) def apt_source_key(self, filename, cfg): """ apt_source_key -- cgit v1.2.3 From 7a141721c5bb0ba2e65191c514a15ff01220ebca Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 14:14:44 +0200 Subject: make checkers happy about unused loop index --- tests/unittests/test_handler/test_handler_apt_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 9edd3df5..3056e9d0 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -219,7 +219,7 @@ class TestAptSourceConfig(TestCase): # check if it added the right ammount of keys calls = [] - for i in range(keynum): + for _ in range(keynum): calls.append(call(('apt-key', 'add', '-'), 'fakekey 1234')) mockobj.assert_has_calls(calls, any_order=True) -- cgit v1.2.3 From 72ff44b4517eacb4f525e1bf7db6787607ff306a Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 14:21:23 +0200 Subject: add triple test for ppa adding --- .../test_handler/test_handler_apt_source.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 3056e9d0..76099035 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -431,5 +431,32 @@ class TestAptSourceConfig(TestCase): # adding ppa should ignore filename (uses add-apt-repository) self.assertFalse(os.path.isfile(self.aptlistfile)) + def test_apt_source_ppa_triple(self): + """ test_apt_source_ppa_triple + Test specification of a ppa + """ + params = self._get_default_params() + cfg1 = {'source': 'ppa:smoser/cloud-init-test', + 'filename': self.aptlistfile} + cfg2 = {'source': 'ppa:smoser/cloud-init-test2', + 'filename': self.aptlistfile2} + cfg3 = {'source': 'ppa:smoser/cloud-init-test3', + 'filename': self.aptlistfile3} + + # default matcher needed for ppa + matcher = re.compile(r'^[\w-]+:\w').search + + with mock.patch.object(util, 'subp') as mockobj: + cc_apt_configure.add_sources([cfg1, cfg2, cfg3], params, + aa_repo_match=matcher) + calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test']), + call(['add-apt-repository', 'ppa:smoser/cloud-init-test2']), + call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'])] + mockobj.assert_has_calls(calls, any_order=True) + + # adding ppa should ignore all filenames (uses add-apt-repository) + self.assertFalse(os.path.isfile(self.aptlistfile)) + self.assertFalse(os.path.isfile(self.aptlistfile2)) + self.assertFalse(os.path.isfile(self.aptlistfile3)) # vi: ts=4 expandtab -- cgit v1.2.3 From d1887b6fb38d378bb35298fadd6bea729952f3a0 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 15:07:37 +0200 Subject: add test_apt_source_basic_dict This is the basic testcase but in the new dictionary format --- tests/unittests/test_handler/test_handler_apt_source.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 76099035..d2370e93 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -103,6 +103,18 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile} self.apt_source_basic(self.aptlistfile, [cfg]) + def test_apt_source_basic_dict(self): + """ test_apt_source_basic_dict + Test Fix deb source string, has to overwrite mirror conf in params. + Test with a filename provided in config. + Provided in a dictionary with filename being the key (new format) + """ + cfg = {self.aptlistfile: {'source': + ('deb http://archive.ubuntu.com/ubuntu' + ' karmic-backports' + ' main universe multiverse restricted')}} + self.apt_source_basic(self.aptlistfile, cfg) + def test_apt_source_basic_triple(self): """ test_apt_source_basic_triple Test Fix three deb source string, has to overwrite mirror conf in -- cgit v1.2.3 From e4968621c802062910d964c2d9c897d7124861b0 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 15:21:55 +0200 Subject: unify basic triple check and add test_apt_src_basic_dict_triple based on it --- .../test_handler/test_handler_apt_source.py | 57 ++++++++++++++++------ 1 file changed, 43 insertions(+), 14 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index d2370e93..f19a78b1 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -115,6 +115,29 @@ class TestAptSourceConfig(TestCase): ' main universe multiverse restricted')}} self.apt_source_basic(self.aptlistfile, cfg) + def apt_source_basic_triple(self, cfg): + """ apt_source_basic_triple + Test Fix three deb source string, has to overwrite mirror conf in + params. Test with filenames provided in config. + generic part to check three files with different content + """ + self.apt_source_basic(self.aptlistfile, cfg) + + # extra verify on two extra files of this test + contents = load_tfile_or_url(self.aptlistfile2) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", "http://archive.ubuntu.com/ubuntu", + "precise-backports", + "main universe multiverse restricted"), + contents, flags=re.IGNORECASE)) + contents = load_tfile_or_url(self.aptlistfile3) + self.assertTrue(re.search(r"%s %s %s %s\n" % + ("deb", "http://archive.ubuntu.com/ubuntu", + "lucid-backports", + "main universe multiverse restricted"), + contents, flags=re.IGNORECASE)) + + def test_apt_source_basic_triple(self): """ test_apt_source_basic_triple Test Fix three deb source string, has to overwrite mirror conf in @@ -132,21 +155,27 @@ class TestAptSourceConfig(TestCase): ' lucid-backports' ' main universe multiverse restricted'), 'filename': self.aptlistfile3} - self.apt_source_basic(self.aptlistfile, [cfg1, cfg2, cfg3]) + self.apt_source_basic_triple([cfg1, cfg2, cfg3]) - # extra verify on two extra files of this test - contents = load_tfile_or_url(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://archive.ubuntu.com/ubuntu", - "precise-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) - contents = load_tfile_or_url(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://archive.ubuntu.com/ubuntu", - "lucid-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + def test_apt_src_basic_dict_triple(self): + """ test_apt_src_basic_dict_triple + Test Fix three deb source string, has to overwrite mirror conf in + params. Test with filenames provided in config. + Provided in a dictionary with filename being the key (new format) + """ + cfg = {self.aptlistfile: {'source': + ('deb http://archive.ubuntu.com/ubuntu' + ' karmic-backports' + ' main universe multiverse restricted')}, + self.aptlistfile2: {'source': + ('deb http://archive.ubuntu.com/ubuntu' + ' precise-backports' + ' main universe multiverse restricted')}, + self.aptlistfile3: {'source': + ('deb http://archive.ubuntu.com/ubuntu' + ' lucid-backports' + ' main universe multiverse restricted')}} + self.apt_source_basic_triple(cfg) def test_apt_source_basic_nofn(self): """ test_apt_source_basic_nofn -- cgit v1.2.3 From 299e3dd9b3769101fa694d7e96895bb89e0375ca Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 16:13:45 +0200 Subject: shorten method names to follow python rules --- .../test_handler/test_handler_apt_source.py | 145 +++++++++++---------- 1 file changed, 76 insertions(+), 69 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index f19a78b1..9a2cc268 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -75,8 +75,8 @@ class TestAptSourceConfig(TestCase): else: return self.join(*args, **kwargs) - def apt_source_basic(self, filename, cfg): - """ apt_source_basic + def apt_src_basic(self, filename, cfg): + """ apt_src_basic Test Fix deb source string, has to overwrite mirror conf in params """ params = self._get_default_params() @@ -92,8 +92,8 @@ class TestAptSourceConfig(TestCase): "main universe multiverse restricted"), contents, flags=re.IGNORECASE)) - def test_apt_source_basic(self): - """ test_apt_source_basic + def test_apt_src_basic(self): + """ test_apt_src_basic Test Fix deb source string, has to overwrite mirror conf in params. Test with a filename provided in config. """ @@ -101,10 +101,10 @@ class TestAptSourceConfig(TestCase): ' karmic-backports' ' main universe multiverse restricted'), 'filename': self.aptlistfile} - self.apt_source_basic(self.aptlistfile, [cfg]) + self.apt_src_basic(self.aptlistfile, [cfg]) - def test_apt_source_basic_dict(self): - """ test_apt_source_basic_dict + def test_apt_src_basic_dict(self): + """ test_apt_src_basic_dict Test Fix deb source string, has to overwrite mirror conf in params. Test with a filename provided in config. Provided in a dictionary with filename being the key (new format) @@ -113,15 +113,15 @@ class TestAptSourceConfig(TestCase): ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' ' main universe multiverse restricted')}} - self.apt_source_basic(self.aptlistfile, cfg) + self.apt_src_basic(self.aptlistfile, cfg) - def apt_source_basic_triple(self, cfg): - """ apt_source_basic_triple + def apt_src_basic_tri(self, cfg): + """ apt_src_basic_tri Test Fix three deb source string, has to overwrite mirror conf in params. Test with filenames provided in config. generic part to check three files with different content """ - self.apt_source_basic(self.aptlistfile, cfg) + self.apt_src_basic(self.aptlistfile, cfg) # extra verify on two extra files of this test contents = load_tfile_or_url(self.aptlistfile2) @@ -138,8 +138,8 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) - def test_apt_source_basic_triple(self): - """ test_apt_source_basic_triple + def test_apt_src_basic_tri(self): + """ test_apt_src_basic_tri Test Fix three deb source string, has to overwrite mirror conf in params. Test with filenames provided in config. """ @@ -155,10 +155,10 @@ class TestAptSourceConfig(TestCase): ' lucid-backports' ' main universe multiverse restricted'), 'filename': self.aptlistfile3} - self.apt_source_basic_triple([cfg1, cfg2, cfg3]) + self.apt_src_basic_tri([cfg1, cfg2, cfg3]) - def test_apt_src_basic_dict_triple(self): - """ test_apt_src_basic_dict_triple + def test_apt_src_basic_dict_tri(self): + """ test_apt_src_basic_dict_tri Test Fix three deb source string, has to overwrite mirror conf in params. Test with filenames provided in config. Provided in a dictionary with filename being the key (new format) @@ -175,10 +175,10 @@ class TestAptSourceConfig(TestCase): ('deb http://archive.ubuntu.com/ubuntu' ' lucid-backports' ' main universe multiverse restricted')}} - self.apt_source_basic_triple(cfg) + self.apt_src_basic_tri(cfg) - def test_apt_source_basic_nofn(self): - """ test_apt_source_basic_nofn + def test_apt_src_basic_nofn(self): + """ test_apt_src_basic_nofn Test Fix deb source string, has to overwrite mirror conf in params. Test without a filename provided in config and test for known fallback. """ @@ -186,10 +186,10 @@ class TestAptSourceConfig(TestCase): ' karmic-backports' ' main universe multiverse restricted')} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_basic(self.fallbackfn, [cfg]) + self.apt_src_basic(self.fallbackfn, [cfg]) - def apt_source_replacement(self, filename, cfg): - """ apt_source_replace + def apt_src_replacement(self, filename, cfg): + """ apt_src_replace Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() @@ -203,27 +203,21 @@ class TestAptSourceConfig(TestCase): "multiverse"), contents, flags=re.IGNORECASE)) - def test_apt_source_replace(self): - """ test_apt_source_replace + def test_apt_src_replace(self): + """ test_apt_src_replace Test Autoreplacement of MIRROR and RELEASE in source specs with Filename being set """ cfg = {'source': 'deb $MIRROR $RELEASE multiverse', 'filename': self.aptlistfile} - self.apt_source_replacement(self.aptlistfile, [cfg]) + self.apt_src_replacement(self.aptlistfile, [cfg]) - def test_apt_source_replace_triple(self): - """ test_apt_source_replace_triple + def apt_src_replace_tri(self, cfg): + """ apt_src_replace_tri Test three autoreplacements of MIRROR and RELEASE in source specs with - Filename being set + generic part """ - cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile} - cfg2 = {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2} - cfg3 = {'source': 'deb $MIRROR $RELEASE universe', - 'filename': self.aptlistfile3} - self.apt_source_replacement(self.aptlistfile, [cfg1, cfg2, cfg3]) + self.apt_src_replacement(self.aptlistfile, cfg) # extra verify on two extra files of this test params = self._get_default_params() @@ -239,17 +233,30 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) - def test_apt_source_replace_nofn(self): - """ test_apt_source_replace_nofn + def test_apt_src_replace_tri(self): + """ test_apt_src_replace_tri + Test three autoreplacements of MIRROR and RELEASE in source specs with + Filename being set + """ + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', + 'filename': self.aptlistfile} + cfg2 = {'source': 'deb $MIRROR $RELEASE main', + 'filename': self.aptlistfile2} + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', + 'filename': self.aptlistfile3} + self.apt_src_replace_tri([cfg1, cfg2, cfg3]) + + def test_apt_src_replace_nofn(self): + """ test_apt_src_replace_nofn Test Autoreplacement of MIRROR and RELEASE in source specs with No filename being set """ cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_replacement(self.fallbackfn, [cfg]) + self.apt_src_replacement(self.fallbackfn, [cfg]) - def apt_source_keyid(self, filename, cfg, keynum): - """ apt_source_keyid + def apt_src_keyid(self, filename, cfg, keynum): + """ apt_src_keyid Test specification of a source + keyid """ params = self._get_default_params() @@ -274,8 +281,8 @@ class TestAptSourceConfig(TestCase): "xenial", "main"), contents, flags=re.IGNORECASE)) - def test_apt_source_keyid(self): - """ test_apt_source_keyid + def test_apt_src_keyid(self): + """ test_apt_src_keyid Test specification of a source + keyid with filename being set """ cfg = {'source': ('deb ' @@ -284,10 +291,10 @@ class TestAptSourceConfig(TestCase): ' xenial main'), 'keyid': "03683F77", 'filename': self.aptlistfile} - self.apt_source_keyid(self.aptlistfile, [cfg], 1) + self.apt_src_keyid(self.aptlistfile, [cfg], 1) - def test_apt_source_keyid_triple(self): - """ test_apt_source_keyid_triple + def test_apt_src_keyid_tri(self): + """ test_apt_src_keyid_tri Test specification of a source + keyid with filename being set Setting three of such, check for content and keys """ @@ -310,7 +317,7 @@ class TestAptSourceConfig(TestCase): 'keyid': "03683F77", 'filename': self.aptlistfile3} - self.apt_source_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) + self.apt_src_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) contents = load_tfile_or_url(self.aptlistfile2) self.assertTrue(re.search(r"%s %s %s %s\n" % ("deb", @@ -326,8 +333,8 @@ class TestAptSourceConfig(TestCase): "xenial", "multiverse"), contents, flags=re.IGNORECASE)) - def test_apt_source_keyid_nofn(self): - """ test_apt_source_keyid_nofn + def test_apt_src_keyid_nofn(self): + """ test_apt_src_keyid_nofn Test specification of a source + keyid without filename being set """ cfg = {'source': ('deb ' @@ -336,10 +343,10 @@ class TestAptSourceConfig(TestCase): ' xenial main'), 'keyid': "03683F77"} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_keyid(self.fallbackfn, [cfg], 1) + self.apt_src_keyid(self.fallbackfn, [cfg], 1) - def apt_source_key(self, filename, cfg): - """ apt_source_key + def apt_src_key(self, filename, cfg): + """ apt_src_key Test specification of a source + key """ params = self._get_default_params() @@ -359,8 +366,8 @@ class TestAptSourceConfig(TestCase): "xenial", "main"), contents, flags=re.IGNORECASE)) - def test_apt_source_key(self): - """ test_apt_source_key + def test_apt_src_key(self): + """ test_apt_src_key Test specification of a source + key with filename being set """ cfg = {'source': ('deb ' @@ -369,10 +376,10 @@ class TestAptSourceConfig(TestCase): ' xenial main'), 'key': "fakekey 4321", 'filename': self.aptlistfile} - self.apt_source_key(self.aptlistfile, cfg) + self.apt_src_key(self.aptlistfile, cfg) - def test_apt_source_key_nofn(self): - """ test_apt_source_key_nofn + def test_apt_src_key_nofn(self): + """ test_apt_src_key_nofn Test specification of a source + key without filename being set """ cfg = {'source': ('deb ' @@ -381,10 +388,10 @@ class TestAptSourceConfig(TestCase): ' xenial main'), 'key': "fakekey 4321"} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): - self.apt_source_key(self.fallbackfn, cfg) + self.apt_src_key(self.fallbackfn, cfg) - def test_apt_source_keyonly(self): - """ test_apt_source_keyonly + def test_apt_src_keyonly(self): + """ test_apt_src_keyonly Test specification key without source """ params = self._get_default_params() @@ -400,8 +407,8 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_source_keyidonly(self): - """ test_apt_source_keyidonly + def test_apt_src_keyidonly(self): + """ test_apt_src_keyidonly Test specification of a keyid without source """ params = self._get_default_params() @@ -417,8 +424,8 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_source_keyid_real(self): - """ test_apt_source_keyid_real + def test_apt_src_keyid_real(self): + """ test_apt_src_keyid_real Test specification of a keyid without source incl up to addition of the key (nothing but add_key_raw mocked) """ @@ -435,8 +442,8 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_source_longkeyid_real(self): - """ test_apt_source_keyid_real + def test_apt_src_longkeyid_real(self): + """ test_apt_src_keyid_real Test specification of a long key fingerprint without source incl up to addition of the key (nothing but add_key_raw mocked) """ @@ -453,8 +460,8 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_source_ppa(self): - """ test_apt_source_ppa + def test_apt_src_ppa(self): + """ test_apt_src_ppa Test specification of a ppa """ params = self._get_default_params() @@ -472,8 +479,8 @@ class TestAptSourceConfig(TestCase): # adding ppa should ignore filename (uses add-apt-repository) self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_source_ppa_triple(self): - """ test_apt_source_ppa_triple + def test_apt_src_ppa_tri(self): + """ test_apt_src_ppa_tri Test specification of a ppa """ params = self._get_default_params() -- cgit v1.2.3 From 23a19bde96d895e97c5fb6dbbe50620fb1130553 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 16:14:04 +0200 Subject: add test_apt_src_replace_dict_tri This includes a test for the weird but valid case in the new dictionary syntax that one sets a key (which is the filename) but overwrites the filename value inside of it. --- tests/unittests/test_handler/test_handler_apt_source.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 9a2cc268..237cf14d 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -246,6 +246,20 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile3} self.apt_src_replace_tri([cfg1, cfg2, cfg3]) + def test_apt_src_replace_dict_tri(self): + """ test_apt_src_replace_dict_tri + Test three autoreplacements of MIRROR and RELEASE in source specs with + Filename being set + Provided in a dictionary with filename being the key (new format) + We also test a new special conditions of the new format that allows + filenames to be overwritten inside the directory entry. + """ + cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, + 'notused': {'source': 'deb $MIRROR $RELEASE main', + 'filename': self.aptlistfile2}, + self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} + self.apt_src_replace_tri(cfg) + def test_apt_src_replace_nofn(self): """ test_apt_src_replace_nofn Test Autoreplacement of MIRROR and RELEASE in source specs with -- cgit v1.2.3 From a63a64a70def97730d2ab544b0df9f87f3484333 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 23 May 2016 16:53:03 +0200 Subject: final pep8 check fixups --- cloudinit/util.py | 2 ++ tests/unittests/test_handler/test_handler_apt_source.py | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/cloudinit/util.py b/cloudinit/util.py index 2931efbd..0773af69 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -335,6 +335,7 @@ def rand_str(strlen=32, select_from=None): select_from = string.ascii_letters + string.digits return "".join([random.choice(select_from) for _x in range(0, strlen)]) + def rand_dict_key(dictionary, postfix=None): if not postfix: postfix = "" @@ -344,6 +345,7 @@ def rand_dict_key(dictionary, postfix=None): break return newkey + def read_conf(fname): try: return load_yaml(load_file(fname), default={}) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 237cf14d..c19904fb 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -55,7 +55,6 @@ class TestAptSourceConfig(TestCase): self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") - @staticmethod def _get_default_params(): """ get_default_params @@ -68,9 +67,9 @@ class TestAptSourceConfig(TestCase): def myjoin(self, *args, **kwargs): """ myjoin - redir into writable tmpdir""" - if (args[0] == "/etc/apt/sources.list.d/" - and args[1] == "cloud_config_sources.list" - and len(args) == 2): + if (args[0] == "/etc/apt/sources.list.d/" and + args[1] == "cloud_config_sources.list" and + len(args) == 2): return self.join(self.tmp, args[0].lstrip("/"), args[1]) else: return self.join(*args, **kwargs) @@ -137,7 +136,6 @@ class TestAptSourceConfig(TestCase): "main universe multiverse restricted"), contents, flags=re.IGNORECASE)) - def test_apt_src_basic_tri(self): """ test_apt_src_basic_tri Test Fix three deb source string, has to overwrite mirror conf in @@ -232,7 +230,6 @@ class TestAptSourceConfig(TestCase): "universe"), contents, flags=re.IGNORECASE)) - def test_apt_src_replace_tri(self): """ test_apt_src_replace_tri Test three autoreplacements of MIRROR and RELEASE in source specs with -- cgit v1.2.3 From 150abceed3ec8d1d5099e855ebc4fce0361be65d Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 24 May 2016 19:07:15 +0200 Subject: add test for the now isolated convert_to_new_format function --- .../test_handler/test_handler_apt_source.py | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index c19904fb..1b2bc58f 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -518,4 +518,33 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile2)) self.assertFalse(os.path.isfile(self.aptlistfile3)) + def test_convert_to_new_format(self): + """ test_convert_to_new_format + Test the conversion of old to new format + And the noop conversion of new to new format as well + """ + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', + 'filename': self.aptlistfile} + cfg2 = {'source': 'deb $MIRROR $RELEASE main', + 'filename': self.aptlistfile2} + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', + 'filename': self.aptlistfile3} + errorlist = [] + checkcfg = {self.aptlistfile: {'filename': self.aptlistfile, + 'source': 'deb $MIRROR $RELEASE ' + 'multiverse'}, + self.aptlistfile2: {'filename': self.aptlistfile2, + 'source': 'deb $MIRROR $RELEASE main'}, + self.aptlistfile3: {'filename': self.aptlistfile3, + 'source': 'deb $MIRROR $RELEASE ' + 'universe'}} + + newcfg = cc_apt_configure.convert_to_new_format([cfg1, cfg2, cfg3], + errorlist) + self.assertEqual(newcfg, checkcfg) + + newcfg2 = cc_apt_configure.convert_to_new_format(newcfg, errorlist) + self.assertEqual(newcfg2, checkcfg) + + # vi: ts=4 expandtab -- cgit v1.2.3 From a84747a8b7a6b527e8ee294e6fc04d70a7090bc5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 25 May 2016 09:32:39 +0200 Subject: make test_apt_srcl_custom independent to where it is executed --- .../test_handler/test_handler_apt_configure_sources_list.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 353422a2..5cf386f8 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -55,8 +55,8 @@ EXPECTED_CONVERTED_CONTENT = ( # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to # newer versions of the distribution. -deb http://archive.ubuntu.com/ubuntu/ xenial main restricted -deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted +deb http://archive.ubuntu.com/ubuntu/ fakerelease main restricted +deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted # FIND_SOMETHING_SPECIAL """) @@ -152,8 +152,10 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): # the second mock restores the original subp with mock.patch.object(util, 'write_file') as mockwrite: with mock.patch.object(util, 'subp', self.subp): - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) + with mock.patch.object(cc_apt_configure, 'get_release', + return_value='fakerelease'): + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) mockwrite.assert_called_once_with( '/etc/apt/sources.list', -- cgit v1.2.3 From 12cf5ce64f0252a67c6a9dcbd9d3c93a1a07ccad Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Wed, 25 May 2016 13:18:50 +0200 Subject: fix inline doc of test_apt_src_longkeyid_real --- tests/unittests/test_handler/test_handler_apt_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 1b2bc58f..753e86a8 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -454,7 +454,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_longkeyid_real(self): - """ test_apt_src_keyid_real + """ test_apt_src_longkeyid_real Test specification of a long key fingerprint without source incl up to addition of the key (nothing but add_key_raw mocked) """ -- cgit v1.2.3 From aa236033b159b691f5ec31885750a8167c63b2a1 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 30 May 2016 12:27:40 +0200 Subject: drop errorlist from convert_to_new_format --- cloudinit/config/cc_apt_configure.py | 12 +++--------- tests/unittests/test_handler/test_handler_apt_source.py | 6 ++---- 2 files changed, 5 insertions(+), 13 deletions(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index ffbf7513..2dd48844 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -198,24 +198,18 @@ def add_key(ent): add_key_raw(ent['key']) -def convert_to_new_format(srclist, errorlist): +def convert_to_new_format(srclist): """ convert_to_new_format convert the old list based format to the new dict based one """ srcdict = {} if isinstance(srclist, list): - fnfallbackused = None for srcent in srclist: if 'filename' not in srcent: # file collides for multiple !filename cases for compatibility # yet we need them all processed, so not same dictionary key srcent['filename'] = "cloud_config_sources.list" key = util.rand_dict_key(srcdict, "cloud_config_sources.list") - if fnfallbackused is not None: - errorlist.append(["multiple apt_source entries without", - "filename will conflict: %s vs %s" % - (srcent, fnfallbackused)]) - fnfallbackused = srcent else: # all with filename use that as key (matching new format) key = srcent['filename'] @@ -223,7 +217,7 @@ def convert_to_new_format(srclist, errorlist): elif isinstance(srclist, dict): srcdict = srclist else: - errorlist.append(["srclist", "unknown apt_sources format"]) + raise ValueError("unknown apt_sources format") return srcdict @@ -242,7 +236,7 @@ def add_sources(srclist, template_params=None, aa_repo_match=None): return False errorlist = [] - srcdict = convert_to_new_format(srclist, errorlist) + srcdict = convert_to_new_format(srclist) for filename in srcdict: ent = srcdict[filename] diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 753e86a8..4536c5b2 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -529,7 +529,6 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile2} cfg3 = {'source': 'deb $MIRROR $RELEASE universe', 'filename': self.aptlistfile3} - errorlist = [] checkcfg = {self.aptlistfile: {'filename': self.aptlistfile, 'source': 'deb $MIRROR $RELEASE ' 'multiverse'}, @@ -539,11 +538,10 @@ class TestAptSourceConfig(TestCase): 'source': 'deb $MIRROR $RELEASE ' 'universe'}} - newcfg = cc_apt_configure.convert_to_new_format([cfg1, cfg2, cfg3], - errorlist) + newcfg = cc_apt_configure.convert_to_new_format([cfg1, cfg2, cfg3]) self.assertEqual(newcfg, checkcfg) - newcfg2 = cc_apt_configure.convert_to_new_format(newcfg, errorlist) + newcfg2 = cc_apt_configure.convert_to_new_format(newcfg) self.assertEqual(newcfg2, checkcfg) -- cgit v1.2.3 From c7b9b2b04490fda948631eecdba1bb678f0e4db9 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 30 May 2016 12:48:00 +0200 Subject: add test for wrong apt_source format --- tests/unittests/test_handler/test_handler_apt_source.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 4536c5b2..fe2ffae5 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -544,5 +544,8 @@ class TestAptSourceConfig(TestCase): newcfg2 = cc_apt_configure.convert_to_new_format(newcfg) self.assertEqual(newcfg2, checkcfg) + with self.assertRaises(ValueError): + cc_apt_configure.convert_to_new_format(5) + # vi: ts=4 expandtab -- cgit v1.2.3 From f495947a701d5629b6dbfd2ff9e01dad7bd5166b Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 3 Jun 2016 14:58:51 -0400 Subject: fix issue with routes on subnets not getting rendered --- cloudinit/net/__init__.py | 2 ++ .../unittests/test_datasource/test_configdrive.py | 41 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index c72b6ff8..49e9d5c2 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -574,6 +574,8 @@ def render_interfaces(network_state): content += iface_start_entry(iface, index) content += iface_add_subnet(iface, subnet) content += iface_add_attrs(iface) + for route in subnet.get('routes', []): + content += render_route(route, indent=" ") else: # ifenslave docs say to auto the slave devices if 'bond-master' in iface: diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index 1364b39d..83d75f9c 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -15,6 +15,7 @@ except ImportError: from contextlib2 import ExitStack from cloudinit import helpers +from cloudinit import net from cloudinit import settings from cloudinit.sources import DataSourceConfigDrive as ds from cloudinit.sources.helpers import openstack @@ -88,9 +89,33 @@ NETWORK_DATA = { ] } +NETWORK_DATA_2 = { + "services": [ + {"type": "dns", "address": "1.1.1.191"}, + {"type": "dns", "address": "1.1.1.4"}], + "networks": [ + {"network_id": "d94bbe94-7abc-48d4-9c82-4628ea26164a", "type": "ipv4", + "netmask": "255.255.255.248", "link": "eth0", + "routes": [{"netmask": "0.0.0.0", "network": "0.0.0.0", + "gateway": "2.2.2.9"}], + "ip_address": "2.2.2.10", "id": "network0-ipv4"}, + {"network_id": "ca447c83-6409-499b-aaef-6ad1ae995348", "type": "ipv4", + "netmask": "255.255.255.224", "link": "eth1", + "routes": [], "ip_address": "3.3.3.24", "id": "network1-ipv4"}], + "links": [ + {"ethernet_mac_address": "fa:16:3e:dd:50:9a", "mtu": 1500, + "type": "vif", "id": "eth0", "vif_id": "vif-foo1"}, + {"ethernet_mac_address": "fa:16:3e:a8:14:69", "mtu": 1500, + "type": "vif", "id": "eth1", "vif_id": "vif-foo2"}] +} + + KNOWN_MACS = { 'fa:16:3e:69:b0:58': 'enp0s1', - 'fa:16:3e:d4:57:ad': 'enp0s2'} + 'fa:16:3e:d4:57:ad': 'enp0s2', + 'fa:16:3e:dd:50:9a': 'foo1', + 'fa:16:3e:a8:14:69': 'foo2', +} CFG_DRIVE_FILES_V2 = { 'ec2/2009-04-04/meta-data.json': json.dumps(EC2_META), @@ -402,6 +427,20 @@ class TestConvertNetworkData(TestCase): self.assertRaises(ValueError, ds.convert_network_data, NETWORK_DATA, known_macs=macs) + def test_conversion_with_route(self): + ncfg = ds.convert_network_data(NETWORK_DATA_2, known_macs=KNOWN_MACS) + # not the best test, but see that we get a route in the + # network config and that it gets rendered to an ENI file + routes = [] + for n in ncfg['config']: + for s in n.get('subnets', []): + routes.extend(s.get('routes', [])) + self.assertIn( + {'network': '0.0.0.0', 'netmask': '0.0.0.0', 'gateway': '2.2.2.9'}, + routes) + eni = net.render_interfaces(net.parse_net_config_data(ncfg)) + self.assertIn("route add default gw 2.2.2.9", eni) + def cfg_ds_from_dir(seed_d): found = ds.read_config_drive(seed_d) -- cgit v1.2.3 From 80931f7008971c9a7705c054fabc29fec7a133e2 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 3 Jun 2016 15:27:32 -0400 Subject: fix tox -e flake8 --- cloudinit/config/cc_apt_configure.py | 4 +- .../test_handler_apt_configure_sources_list.py | 17 +++--- .../test_handler/test_handler_apt_source.py | 66 +++++++++++----------- 3 files changed, 43 insertions(+), 44 deletions(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index d603f417..7a9777c0 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -199,8 +199,8 @@ def add_key(ent): def convert_to_new_format(srclist): - """ convert_to_new_format - convert the old list based format to the new dict based one + """convert_to_new_format + convert the old list based format to the new dict based one """ srcdict = {} if isinstance(srclist, list): diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 5cf386f8..5d0417a2 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -3,7 +3,6 @@ Test templating of sources list """ import logging import os -import re import shutil import tempfile @@ -62,14 +61,14 @@ deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted def load_tfile_or_url(*args, **kwargs): - """ load_tfile_or_url + """load_tfile_or_url load file and return content after decoding """ return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): - """ TestAptSourceConfigSourceList + """TestAptSourceConfigSourceList Main Class to test sources list rendering """ def setUp(self): @@ -89,7 +88,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): return cloud.Cloud(myds, paths, {}, mydist, None) def apt_source_list(self, distro, mirror, mirrorcheck=None): - """ apt_source_list + """apt_source_list Test rendering of a source.list from template for a given distro """ if mirrorcheck is None: @@ -115,19 +114,19 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): {'codename': '', 'primary': mirrorcheck, 'mirror': mirrorcheck}) def test_apt_source_list_debian(self): - """ test_apt_source_list_debian + """test_apt_source_list_debian Test rendering of a source.list from template for debian """ self.apt_source_list('debian', 'http://httpredir.debian.org/debian') def test_apt_source_list_ubuntu(self): - """ test_apt_source_list_ubuntu + """test_apt_source_list_ubuntu Test rendering of a source.list from template for ubuntu """ self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') def test_apt_srcl_debian_mirrorfail(self): - """ test_apt_source_list_debian_mirrorfail + """test_apt_source_list_debian_mirrorfail Test rendering of a source.list from template for debian """ self.apt_source_list('debian', ['http://does.not.exist', @@ -135,7 +134,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): 'http://httpredir.debian.org/debian') def test_apt_srcl_ubuntu_mirrorfail(self): - """ test_apt_source_list_ubuntu_mirrorfail + """test_apt_source_list_ubuntu_mirrorfail Test rendering of a source.list from template for ubuntu """ self.apt_source_list('ubuntu', ['http://does.not.exist', @@ -143,7 +142,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): 'http://archive.ubuntu.com/ubuntu/') def test_apt_srcl_custom(self): - """ test_apt_srcl_custom + """test_apt_srcl_custom Test rendering from a custom source.list template """ cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index fe2ffae5..4dbe69f0 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -33,14 +33,14 @@ S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== def load_tfile_or_url(*args, **kwargs): - """ load_tfile_or_url + """load_tfile_or_url load file and return content after decoding """ return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) class TestAptSourceConfig(TestCase): - """ TestAptSourceConfig + """TestAptSourceConfig Main Class to test apt_source configs """ def setUp(self): @@ -57,7 +57,7 @@ class TestAptSourceConfig(TestCase): @staticmethod def _get_default_params(): - """ get_default_params + """get_default_params Get the most basic default mrror and release info to be used in tests """ params = {} @@ -66,7 +66,7 @@ class TestAptSourceConfig(TestCase): return params def myjoin(self, *args, **kwargs): - """ myjoin - redir into writable tmpdir""" + """myjoin - redir into writable tmpdir""" if (args[0] == "/etc/apt/sources.list.d/" and args[1] == "cloud_config_sources.list" and len(args) == 2): @@ -75,7 +75,7 @@ class TestAptSourceConfig(TestCase): return self.join(*args, **kwargs) def apt_src_basic(self, filename, cfg): - """ apt_src_basic + """apt_src_basic Test Fix deb source string, has to overwrite mirror conf in params """ params = self._get_default_params() @@ -92,7 +92,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_basic(self): - """ test_apt_src_basic + """test_apt_src_basic Test Fix deb source string, has to overwrite mirror conf in params. Test with a filename provided in config. """ @@ -103,7 +103,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic(self.aptlistfile, [cfg]) def test_apt_src_basic_dict(self): - """ test_apt_src_basic_dict + """test_apt_src_basic_dict Test Fix deb source string, has to overwrite mirror conf in params. Test with a filename provided in config. Provided in a dictionary with filename being the key (new format) @@ -115,7 +115,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic(self.aptlistfile, cfg) def apt_src_basic_tri(self, cfg): - """ apt_src_basic_tri + """apt_src_basic_tri Test Fix three deb source string, has to overwrite mirror conf in params. Test with filenames provided in config. generic part to check three files with different content @@ -137,7 +137,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_basic_tri(self): - """ test_apt_src_basic_tri + """test_apt_src_basic_tri Test Fix three deb source string, has to overwrite mirror conf in params. Test with filenames provided in config. """ @@ -156,7 +156,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic_tri([cfg1, cfg2, cfg3]) def test_apt_src_basic_dict_tri(self): - """ test_apt_src_basic_dict_tri + """test_apt_src_basic_dict_tri Test Fix three deb source string, has to overwrite mirror conf in params. Test with filenames provided in config. Provided in a dictionary with filename being the key (new format) @@ -176,7 +176,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic_tri(cfg) def test_apt_src_basic_nofn(self): - """ test_apt_src_basic_nofn + """test_apt_src_basic_nofn Test Fix deb source string, has to overwrite mirror conf in params. Test without a filename provided in config and test for known fallback. """ @@ -187,7 +187,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic(self.fallbackfn, [cfg]) def apt_src_replacement(self, filename, cfg): - """ apt_src_replace + """apt_src_replace Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() @@ -202,7 +202,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_replace(self): - """ test_apt_src_replace + """test_apt_src_replace Test Autoreplacement of MIRROR and RELEASE in source specs with Filename being set """ @@ -211,7 +211,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_replacement(self.aptlistfile, [cfg]) def apt_src_replace_tri(self, cfg): - """ apt_src_replace_tri + """apt_src_replace_tri Test three autoreplacements of MIRROR and RELEASE in source specs with generic part """ @@ -231,7 +231,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_replace_tri(self): - """ test_apt_src_replace_tri + """test_apt_src_replace_tri Test three autoreplacements of MIRROR and RELEASE in source specs with Filename being set """ @@ -244,7 +244,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_replace_tri([cfg1, cfg2, cfg3]) def test_apt_src_replace_dict_tri(self): - """ test_apt_src_replace_dict_tri + """test_apt_src_replace_dict_tri Test three autoreplacements of MIRROR and RELEASE in source specs with Filename being set Provided in a dictionary with filename being the key (new format) @@ -252,13 +252,13 @@ class TestAptSourceConfig(TestCase): filenames to be overwritten inside the directory entry. """ cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, - 'notused': {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2}, + 'notused': {'source': 'deb $MIRROR $RELEASE main', + 'filename': self.aptlistfile2}, self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} self.apt_src_replace_tri(cfg) def test_apt_src_replace_nofn(self): - """ test_apt_src_replace_nofn + """test_apt_src_replace_nofn Test Autoreplacement of MIRROR and RELEASE in source specs with No filename being set """ @@ -267,7 +267,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_replacement(self.fallbackfn, [cfg]) def apt_src_keyid(self, filename, cfg, keynum): - """ apt_src_keyid + """apt_src_keyid Test specification of a source + keyid """ params = self._get_default_params() @@ -293,7 +293,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_keyid(self): - """ test_apt_src_keyid + """test_apt_src_keyid Test specification of a source + keyid with filename being set """ cfg = {'source': ('deb ' @@ -305,7 +305,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_keyid(self.aptlistfile, [cfg], 1) def test_apt_src_keyid_tri(self): - """ test_apt_src_keyid_tri + """test_apt_src_keyid_tri Test specification of a source + keyid with filename being set Setting three of such, check for content and keys """ @@ -345,7 +345,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_keyid_nofn(self): - """ test_apt_src_keyid_nofn + """test_apt_src_keyid_nofn Test specification of a source + keyid without filename being set """ cfg = {'source': ('deb ' @@ -357,7 +357,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_keyid(self.fallbackfn, [cfg], 1) def apt_src_key(self, filename, cfg): - """ apt_src_key + """apt_src_key Test specification of a source + key """ params = self._get_default_params() @@ -378,7 +378,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_key(self): - """ test_apt_src_key + """test_apt_src_key Test specification of a source + key with filename being set """ cfg = {'source': ('deb ' @@ -390,7 +390,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_key(self.aptlistfile, cfg) def test_apt_src_key_nofn(self): - """ test_apt_src_key_nofn + """test_apt_src_key_nofn Test specification of a source + key without filename being set """ cfg = {'source': ('deb ' @@ -402,7 +402,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_key(self.fallbackfn, cfg) def test_apt_src_keyonly(self): - """ test_apt_src_keyonly + """test_apt_src_keyonly Test specification key without source """ params = self._get_default_params() @@ -419,7 +419,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_keyidonly(self): - """ test_apt_src_keyidonly + """test_apt_src_keyidonly Test specification of a keyid without source """ params = self._get_default_params() @@ -436,7 +436,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_keyid_real(self): - """ test_apt_src_keyid_real + """test_apt_src_keyid_real Test specification of a keyid without source incl up to addition of the key (nothing but add_key_raw mocked) """ @@ -454,7 +454,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_longkeyid_real(self): - """ test_apt_src_longkeyid_real + """test_apt_src_longkeyid_real Test specification of a long key fingerprint without source incl up to addition of the key (nothing but add_key_raw mocked) """ @@ -472,7 +472,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_ppa(self): - """ test_apt_src_ppa + """test_apt_src_ppa Test specification of a ppa """ params = self._get_default_params() @@ -491,7 +491,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_ppa_tri(self): - """ test_apt_src_ppa_tri + """test_apt_src_ppa_tri Test specification of a ppa """ params = self._get_default_params() @@ -519,7 +519,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile3)) def test_convert_to_new_format(self): - """ test_convert_to_new_format + """test_convert_to_new_format Test the conversion of old to new format And the noop conversion of new to new format as well """ -- cgit v1.2.3 From 6c0fc6df1cc332dc3d322961297f8219a3a3048b Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 3 Jun 2016 22:56:45 -0400 Subject: tests: fix apt tests to run inside ubuntu build environment This just mocks out use of lsb_release as it is not available in a build environment. Additionally mocks out use of getkeybyid. This admittedly makes the test for a long key fingerprint not useful as it was broken only inside getkeybyid. Also fix 'make yaml' for cloud-config.txt --- doc/examples/cloud-config.txt | 6 +++--- .../test_handler_apt_configure_sources_list.py | 5 +++-- .../test_handler/test_handler_apt_source.py | 20 +++++++++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) (limited to 'tests') diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt index 62b297bc..3cc9c055 100644 --- a/doc/examples/cloud-config.txt +++ b/doc/examples/cloud-config.txt @@ -125,11 +125,11 @@ add_apt_repo_match: '^[\w-]+:\w' # better merging between multiple input files than a list like: # cloud-config1 # sources: - s1: {'key': 'key1', 'source': 'source1'} +# s1: {'key': 'key1', 'source': 'source1'} # cloud-config2 # sources: - s2: {'key': 'key2'} - s1: {filename: 'foo'} +# s2: {'key': 'key2'} +# s1: {filename: 'foo'} # this would be merged to #sources: # s1: diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 5d0417a2..5f1dd427 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -103,8 +103,9 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): with mock.patch.object(templater, 'render_to_file') as mocktmpl: with mock.patch.object(os.path, 'isfile', return_value=True) as mockisfile: - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) + with mock.patch.object(util, 'rename'): + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) mockisfile.assert_any_call( ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 4dbe69f0..fa28c916 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -43,6 +43,8 @@ class TestAptSourceConfig(TestCase): """TestAptSourceConfig Main Class to test apt_source configs """ + release = "fantastic" + def setUp(self): super(TestAptSourceConfig, self).setUp() self.tmp = tempfile.mkdtemp() @@ -55,8 +57,12 @@ class TestAptSourceConfig(TestCase): self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") - @staticmethod - def _get_default_params(): + patcher = mock.patch("cloudinit.config.cc_apt_configure.get_release") + get_rel = patcher.start() + get_rel.return_value = self.release + self.addCleanup(patcher.stop) + + def _get_default_params(self): """get_default_params Get the most basic default mrror and release info to be used in tests """ @@ -438,7 +444,7 @@ class TestAptSourceConfig(TestCase): def test_apt_src_keyid_real(self): """test_apt_src_keyid_real Test specification of a keyid without source incl - up to addition of the key (nothing but add_key_raw mocked) + up to addition of the key (add_key_raw, getkeybyid mocked) """ keyid = "03683F77" params = self._get_default_params() @@ -446,7 +452,9 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile} with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: - cc_apt_configure.add_sources([cfg], params) + with mock.patch.object(cc_apt_configure, 'getkeybyid') as gkbi: + gkbi.return_value = EXPECTEDKEY + cc_apt_configure.add_sources([cfg], params) mockobj.assert_called_with(EXPECTEDKEY) @@ -464,7 +472,9 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile} with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: - cc_apt_configure.add_sources([cfg], params) + with mock.patch.object(cc_apt_configure, 'getkeybyid') as gkbi: + gkbi.return_value = EXPECTEDKEY + cc_apt_configure.add_sources([cfg], params) mockobj.assert_called_with(EXPECTEDKEY) -- cgit v1.2.3 From 3f38059d1397ed8e16000d2246f1b1f01929bf5e Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 08:56:55 +0200 Subject: unify testing of real gpg key adds --- .../test_handler/test_handler_apt_source.py | 43 +++++++++------------- 1 file changed, 17 insertions(+), 26 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index fa28c916..fc767ece 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -441,45 +441,36 @@ class TestAptSourceConfig(TestCase): # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_src_keyid_real(self): - """test_apt_src_keyid_real - Test specification of a keyid without source incl - up to addition of the key (add_key_raw, getkeybyid mocked) + def apt_src_keyid_real(self, cfg, expectedkey): + """apt_src_keyid_real + Test specification of a keyid without source including + up to addition of the key (nothing but add_key_raw mocked to keep the + environment as is) """ - keyid = "03683F77" params = self._get_default_params() - cfg = {'keyid': keyid, - 'filename': self.aptlistfile} - with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: - with mock.patch.object(cc_apt_configure, 'getkeybyid') as gkbi: - gkbi.return_value = EXPECTEDKEY - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_sources([cfg], params) - mockobj.assert_called_with(EXPECTEDKEY) + mockobj.assert_called_with(expectedkey) # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) - def test_apt_src_longkeyid_real(self): - """test_apt_src_longkeyid_real - Test specification of a long key fingerprint without source incl - up to addition of the key (nothing but add_key_raw mocked) - """ - keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" - params = self._get_default_params() + def test_apt_src_keyid_real(self): + """test_apt_src_keyid_real - Test keyid including key add""" + keyid = "03683F77" cfg = {'keyid': keyid, 'filename': self.aptlistfile} - with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: - with mock.patch.object(cc_apt_configure, 'getkeybyid') as gkbi: - gkbi.return_value = EXPECTEDKEY - cc_apt_configure.add_sources([cfg], params) + self.apt_src_keyid_real(cfg, EXPECTEDKEY) - mockobj.assert_called_with(EXPECTEDKEY) + def test_apt_src_longkeyid_real(self): + """test_apt_src_longkeyid_real - Test long keyid including key add""" + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" + cfg = {'keyid': keyid, + 'filename': self.aptlistfile} - # filename should be ignored on key only - self.assertFalse(os.path.isfile(self.aptlistfile)) + self.apt_src_keyid_real(cfg, EXPECTEDKEY) def test_apt_src_ppa(self): """test_apt_src_ppa -- cgit v1.2.3 From 1b32f10f431aa9138346f20e421442bf935399b5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 08:57:48 +0200 Subject: make _get_default_params method static --- tests/unittests/test_handler/test_handler_apt_source.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index fc767ece..4d8ae46a 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -62,7 +62,8 @@ class TestAptSourceConfig(TestCase): get_rel.return_value = self.release self.addCleanup(patcher.stop) - def _get_default_params(self): + @staticmethod + def _get_default_params(): """get_default_params Get the most basic default mrror and release info to be used in tests """ -- cgit v1.2.3 From eee526be37d47bae4196b88c3b2ecd4fdf0bbc8c Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 09:04:29 +0200 Subject: test alternate keyserver --- tests/unittests/test_handler/test_handler_apt_source.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 4d8ae46a..a4878124 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -473,6 +473,15 @@ class TestAptSourceConfig(TestCase): self.apt_src_keyid_real(cfg, EXPECTEDKEY) + def test_apt_src_longkeyid_ks_real(self): + """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" + cfg = {'keyid': keyid, + 'keyserver': 'keys.gnupg.net', + 'filename': self.aptlistfile} + + self.apt_src_keyid_real(cfg, EXPECTEDKEY) + def test_apt_src_ppa(self): """test_apt_src_ppa Test specification of a ppa -- cgit v1.2.3 From 89688593e9358433cd9383d1bdfae12dbcd58f72 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 13:36:23 +0200 Subject: add_key_raw - make exceptions more specific --- cloudinit/config/cc_apt_configure.py | 4 +-- cloudinit/util.py | 1 + .../test_handler/test_handler_apt_source.py | 38 +++++++++++++++++++--- 3 files changed, 36 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index 2f270662..a9ac6ea8 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -154,7 +154,7 @@ def add_key_raw(key): try: util.subp(('apt-key', 'add', '-'), key) except util.ProcessExecutionError: - raise Exception('failed add key') + raise ValueError('failed to add apt GPG Key to apt keyring') def add_key(ent): @@ -221,7 +221,7 @@ def add_sources(srclist, template_params=None, aa_repo_match=None): # keys can be added without specifying a source try: add_key(ent) - except Exception as detail: + except ValueError as detail: errorlist.append([ent, detail]) if 'source' not in ent: diff --git a/cloudinit/util.py b/cloudinit/util.py index d3b14f72..a1622946 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2244,6 +2244,7 @@ def gpg_export_armour(key): def gpg_recv_key(key, keyserver): """Receive gpg key from the specified keyserver""" + print("ORIGINAL gpg_recv_key") try: subp(["gpg", "--keyserver", keyserver, "--recv", key], capture=True) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index a4878124..65e375a0 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -4,6 +4,7 @@ Testing various config variations of the apt_source config import os import re import shutil +import socket import tempfile try: @@ -56,6 +57,7 @@ class TestAptSourceConfig(TestCase): # mock fallback filename into writable tmp dir self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") + self.orig_gpg_recv_key = util.gpg_recv_key patcher = mock.patch("cloudinit.config.cc_apt_configure.get_release") get_rel = patcher.start() @@ -445,14 +447,40 @@ class TestAptSourceConfig(TestCase): def apt_src_keyid_real(self, cfg, expectedkey): """apt_src_keyid_real Test specification of a keyid without source including - up to addition of the key (nothing but add_key_raw mocked to keep the + up to addition of the key (add_key_raw mocked to keep the environment as is) """ params = self._get_default_params() - with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: - cc_apt_configure.add_sources([cfg], params) - mockobj.assert_called_with(expectedkey) + def fake_gpg_recv_key(self, key, keyserver): + """try original gpg_recv_key, but allow fall back""" + try: + print("Try orig orig_gpg_recv_key") + self.orig_gpg_recv_key(key, keyserver) + except ValueError: + print("Fail, test net") + # if this is a networking issue mock it's effect + testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + testsock.connect((keyserver, 80)) + testsock.close() + except socket.error: + # as fallback add the known key as a working recv would + print("Fallback import expectedkey") + util.subp(("gpg", "--import", "-"), EXPECTEDKEY) + + print("FOO") + with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockkey: + with mock.patch.object(util, 'gpg_recv_key', + side_effect=fake_gpg_recv_key) as mockrecv: + cc_apt_configure.add_sources([cfg], params) + + # since we might mock the recv path ensure it is called right + mockrecv.assert_called_with(cfg['keyid'], + keyserver=cfg.get('keyserver', + 'keyserver.ubuntu.com')) + # no matter if really imported or faked, ensure we add the right key + mockkey.assert_called_with(expectedkey) # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) @@ -477,7 +505,7 @@ class TestAptSourceConfig(TestCase): """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" cfg = {'keyid': keyid, - 'keyserver': 'keys.gnupg.net', + 'keyserver': 'knorz.gnupg.net', 'filename': self.aptlistfile} self.apt_src_keyid_real(cfg, EXPECTEDKEY) -- cgit v1.2.3 From e9b333b49954d5863f8b53581454179b0363d978 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 13:52:31 +0200 Subject: fixup key tests for cases where network isn't available --- cloudinit/util.py | 1 - tests/unittests/test_handler/test_handler_apt_source.py | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/cloudinit/util.py b/cloudinit/util.py index a1622946..d3b14f72 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2244,7 +2244,6 @@ def gpg_export_armour(key): def gpg_recv_key(key, keyserver): """Receive gpg key from the specified keyserver""" - print("ORIGINAL gpg_recv_key") try: subp(["gpg", "--keyserver", keyserver, "--recv", key], capture=True) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 65e375a0..c7eeb64c 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -452,13 +452,11 @@ class TestAptSourceConfig(TestCase): """ params = self._get_default_params() - def fake_gpg_recv_key(self, key, keyserver): + def fake_gpg_recv_key(key, keyserver): """try original gpg_recv_key, but allow fall back""" try: - print("Try orig orig_gpg_recv_key") self.orig_gpg_recv_key(key, keyserver) - except ValueError: - print("Fail, test net") + except ValueError as error: # if this is a networking issue mock it's effect testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: @@ -466,10 +464,8 @@ class TestAptSourceConfig(TestCase): testsock.close() except socket.error: # as fallback add the known key as a working recv would - print("Fallback import expectedkey") util.subp(("gpg", "--import", "-"), EXPECTEDKEY) - print("FOO") with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', side_effect=fake_gpg_recv_key) as mockrecv: -- cgit v1.2.3 From c1ad09609805c6c4f5262eb21533799af814379a Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 14:23:55 +0200 Subject: rename add_key / add_source to add_apt_key / add_apt_source The functions clearly are apt specific so the name should reflect that. --- cloudinit/config/cc_apt_configure.py | 14 ++++++------ .../test_handler/test_handler_apt_source.py | 25 +++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index a9ac6ea8..91a117f5 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -79,8 +79,8 @@ def handle(name, cfg, cloud, log, _args): def matcher(x): return False - errors = add_sources(cfg['apt_sources'], params, - aa_repo_match=matcher) + errors = add_apt_sources(cfg['apt_sources'], params, + aa_repo_match=matcher) for e in errors: log.warn("Add source error: %s", ':'.join(e)) @@ -146,7 +146,7 @@ def generate_sources_list(cfg, codename, mirrors, cloud, log): templater.render_to_file(template_fn, '/etc/apt/sources.list', params) -def add_key_raw(key): +def add_apt_key_raw(key): """ actual adding of a key as defined in key argument to the system @@ -157,7 +157,7 @@ def add_key_raw(key): raise ValueError('failed to add apt GPG Key to apt keyring') -def add_key(ent): +def add_apt_key(ent): """ add key to the system as defined in ent (if any) supports raw keys or keyid's @@ -170,7 +170,7 @@ def add_key(ent): ent['key'] = util.getkeybyid(ent['keyid'], keyserver) if 'key' in ent: - add_key_raw(ent['key']) + add_apt_key_raw(ent['key']) def convert_to_new_format(srclist): @@ -197,7 +197,7 @@ def convert_to_new_format(srclist): return srcdict -def add_sources(srclist, template_params=None, aa_repo_match=None): +def add_apt_sources(srclist, template_params=None, aa_repo_match=None): """ add entries in /etc/apt/sources.list.d for each abbreviated sources.list entry in 'srclist'. When rendering template, also @@ -220,7 +220,7 @@ def add_sources(srclist, template_params=None, aa_repo_match=None): # keys can be added without specifying a source try: - add_key(ent) + add_apt_key(ent) except ValueError as detail: errorlist.append([ent, detail]) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index c7eeb64c..4a720213 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -89,7 +89,7 @@ class TestAptSourceConfig(TestCase): """ params = self._get_default_params() - cc_apt_configure.add_sources(cfg, params) + cc_apt_configure.add_apt_sources(cfg, params) self.assertTrue(os.path.isfile(filename)) @@ -200,7 +200,7 @@ class TestAptSourceConfig(TestCase): Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() - cc_apt_configure.add_sources(cfg, params) + cc_apt_configure.add_apt_sources(cfg, params) self.assertTrue(os.path.isfile(filename)) @@ -283,7 +283,7 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: - cc_apt_configure.add_sources(cfg, params) + cc_apt_configure.add_apt_sources(cfg, params) # check if it added the right ammount of keys calls = [] @@ -372,7 +372,7 @@ class TestAptSourceConfig(TestCase): params = self._get_default_params() with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') @@ -419,7 +419,7 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile} with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) mockobj.assert_called_once_with(('apt-key', 'add', '-'), 'fakekey 4242') @@ -437,7 +437,7 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp', return_value=('fakekey 1212', '')) as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1212') @@ -447,7 +447,7 @@ class TestAptSourceConfig(TestCase): def apt_src_keyid_real(self, cfg, expectedkey): """apt_src_keyid_real Test specification of a keyid without source including - up to addition of the key (add_key_raw mocked to keep the + up to addition of the key (add_apt_key_raw mocked to keep the environment as is) """ params = self._get_default_params() @@ -466,10 +466,10 @@ class TestAptSourceConfig(TestCase): # as fallback add the known key as a working recv would util.subp(("gpg", "--import", "-"), EXPECTEDKEY) - with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockkey: + with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', side_effect=fake_gpg_recv_key) as mockrecv: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) # since we might mock the recv path ensure it is called right mockrecv.assert_called_with(cfg['keyid'], @@ -518,7 +518,8 @@ class TestAptSourceConfig(TestCase): matcher = re.compile(r'^[\w-]+:\w').search with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) + cc_apt_configure.add_apt_sources([cfg], params, + aa_repo_match=matcher) mockobj.assert_called_once_with(['add-apt-repository', 'ppa:smoser/cloud-init-test']) @@ -541,8 +542,8 @@ class TestAptSourceConfig(TestCase): matcher = re.compile(r'^[\w-]+:\w').search with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg1, cfg2, cfg3], params, - aa_repo_match=matcher) + cc_apt_configure.add_apt_sources([cfg1, cfg2, cfg3], params, + aa_repo_match=matcher) calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test']), call(['add-apt-repository', 'ppa:smoser/cloud-init-test2']), call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'])] -- cgit v1.2.3 From a3b234357ce1e1c7b8d9411d0928873752e2d107 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 14:34:45 +0200 Subject: only test the apt feature in environments with apt --- .../test_handler/test_handler_apt_configure_sources_list.py | 4 ++++ tests/unittests/test_handler/test_handler_apt_source.py | 5 +++++ 2 files changed, 9 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 5f1dd427..27e332d5 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -24,6 +24,8 @@ from .. import helpers as t_help LOG = logging.getLogger(__name__) +BIN_APT = "/usr/bin/apt" + YAML_TEXT_CUSTOM_SL = """ apt_mirror: http://archive.ubuntu.com/ubuntu/ apt_custom_sources_list: | @@ -67,6 +69,8 @@ def load_tfile_or_url(*args, **kwargs): return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) +# This feature is apt specific and thereby is disabled in environments without +@t_help.skipIf(not os.path.isfile(BIN_APT), "no apt") class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """TestAptSourceConfigSourceList Main Class to test sources list rendering diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 4a720213..223af764 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -17,6 +17,9 @@ from cloudinit.config import cc_apt_configure from cloudinit import util from ..helpers import TestCase +from .. import helpers as t_help + +BIN_APT = "/usr/bin/apt" EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 @@ -40,6 +43,8 @@ def load_tfile_or_url(*args, **kwargs): return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) +# This feature is apt specific and thereby is disabled in environments without +@t_help.skipIf(not os.path.isfile(BIN_APT), "no apt") class TestAptSourceConfig(TestCase): """TestAptSourceConfig Main Class to test apt_source configs -- cgit v1.2.3 From 1210eee8d6bd70817df10f2865af10c13553102b Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 14:48:21 +0200 Subject: fix alternate keyserver dns The intentionally false name was meant for testing of abug, now we can and should use a proper third party keyserver. --- tests/unittests/test_handler/test_handler_apt_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 223af764..a184e7de 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -506,7 +506,7 @@ class TestAptSourceConfig(TestCase): """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" cfg = {'keyid': keyid, - 'keyserver': 'knorz.gnupg.net', + 'keyserver': 'keys.gnupg.net', 'filename': self.aptlistfile} self.apt_src_keyid_real(cfg, EXPECTEDKEY) -- cgit v1.2.3 From 01c47d9f3366a1535fbd50bd7be8f4e51f3f3b4b Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 14:57:56 +0200 Subject: apt_src_keyid_real: drop unused exception variable --- tests/unittests/test_handler/test_handler_apt_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index a184e7de..0b2aa9e5 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -461,7 +461,7 @@ class TestAptSourceConfig(TestCase): """try original gpg_recv_key, but allow fall back""" try: self.orig_gpg_recv_key(key, keyserver) - except ValueError as error: + except ValueError: # if this is a networking issue mock it's effect testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: -- cgit v1.2.3 From 9874eff179410d1cf48c7a02623c3b6bda545344 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 15:14:34 +0200 Subject: remove check that fails if user has installed tested keys The overall check for the expected key is a superset and would spot an issue. --- tests/unittests/test_handler/test_handler_apt_source.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 0b2aa9e5..9aa6ff71 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -473,13 +473,9 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', - side_effect=fake_gpg_recv_key) as mockrecv: + side_effect=fake_gpg_recv_key): cc_apt_configure.add_apt_sources([cfg], params) - # since we might mock the recv path ensure it is called right - mockrecv.assert_called_with(cfg['keyid'], - keyserver=cfg.get('keyserver', - 'keyserver.ubuntu.com')) # no matter if really imported or faked, ensure we add the right key mockkey.assert_called_with(expectedkey) -- cgit v1.2.3 From bb0bf81cb373fde0c59464127633ebc7527f2be5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 15:48:25 +0200 Subject: capture output of gpg calls to avoid messing up stdout/stderr --- cloudinit/util.py | 2 +- tests/unittests/test_handler/test_handler_apt_source.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/cloudinit/util.py b/cloudinit/util.py index d3b14f72..6d16532d 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2254,7 +2254,7 @@ def gpg_recv_key(key, keyserver): def gpg_delete_key(key): """Delete the specified key from the local gpg ring""" - subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=False) + subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) def getkeybyid(keyid, keyserver): diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 9aa6ff71..ea8aa17a 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -469,7 +469,8 @@ class TestAptSourceConfig(TestCase): testsock.close() except socket.error: # as fallback add the known key as a working recv would - util.subp(("gpg", "--import", "-"), EXPECTEDKEY) + util.subp(("gpg", "--import", "-"), EXPECTEDKEY, + capture=True) with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', -- cgit v1.2.3 From 5d74451a5838c346fcbb4b0eee0d817fbf84128a Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 16:47:42 +0200 Subject: make unittests more readable By moving the "what it does" into the first line of the docstrings output becomes much more useful. --- .../test_handler_apt_configure_sources_list.py | 20 ++--- .../test_handler/test_handler_apt_source.py | 92 +++++----------------- 2 files changed, 24 insertions(+), 88 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 27e332d5..20e61995 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -119,37 +119,27 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): {'codename': '', 'primary': mirrorcheck, 'mirror': mirrorcheck}) def test_apt_source_list_debian(self): - """test_apt_source_list_debian - Test rendering of a source.list from template for debian - """ + """Test rendering of a source.list from template for debian""" self.apt_source_list('debian', 'http://httpredir.debian.org/debian') def test_apt_source_list_ubuntu(self): - """test_apt_source_list_ubuntu - Test rendering of a source.list from template for ubuntu - """ + """Test rendering of a source.list from template for ubuntu""" self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') def test_apt_srcl_debian_mirrorfail(self): - """test_apt_source_list_debian_mirrorfail - Test rendering of a source.list from template for debian - """ + """Test rendering of a source.list from template for debian""" self.apt_source_list('debian', ['http://does.not.exist', 'http://httpredir.debian.org/debian'], 'http://httpredir.debian.org/debian') def test_apt_srcl_ubuntu_mirrorfail(self): - """test_apt_source_list_ubuntu_mirrorfail - Test rendering of a source.list from template for ubuntu - """ + """Test rendering of a source.list from template for ubuntu""" self.apt_source_list('ubuntu', ['http://does.not.exist', 'http://archive.ubuntu.com/ubuntu/'], 'http://archive.ubuntu.com/ubuntu/') def test_apt_srcl_custom(self): - """test_apt_srcl_custom - Test rendering from a custom source.list template - """ + """Test rendering from a custom source.list template""" cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) mycloud = self._get_cloud('ubuntu') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index ea8aa17a..1f76e770 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -106,10 +106,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_basic(self): - """test_apt_src_basic - Test Fix deb source string, has to overwrite mirror conf in params. - Test with a filename provided in config. - """ + """Test deb source string, overwrite mirror and filename""" cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' ' main universe multiverse restricted'), @@ -117,11 +114,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic(self.aptlistfile, [cfg]) def test_apt_src_basic_dict(self): - """test_apt_src_basic_dict - Test Fix deb source string, has to overwrite mirror conf in params. - Test with a filename provided in config. - Provided in a dictionary with filename being the key (new format) - """ + """Test deb source string, overwrite mirror and filename (dict)""" cfg = {self.aptlistfile: {'source': ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' @@ -151,10 +144,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_basic_tri(self): - """test_apt_src_basic_tri - Test Fix three deb source string, has to overwrite mirror conf in - params. Test with filenames provided in config. - """ + """Test Fix three deb source string with filenames""" cfg1 = {'source': ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' ' main universe multiverse restricted'), @@ -170,11 +160,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic_tri([cfg1, cfg2, cfg3]) def test_apt_src_basic_dict_tri(self): - """test_apt_src_basic_dict_tri - Test Fix three deb source string, has to overwrite mirror conf in - params. Test with filenames provided in config. - Provided in a dictionary with filename being the key (new format) - """ + """Test Fix three deb source string with filenames (dict)""" cfg = {self.aptlistfile: {'source': ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' @@ -190,10 +176,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_basic_tri(cfg) def test_apt_src_basic_nofn(self): - """test_apt_src_basic_nofn - Test Fix deb source string, has to overwrite mirror conf in params. - Test without a filename provided in config and test for known fallback. - """ + """Test Fix three deb source string without filenames (dict)""" cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' ' karmic-backports' ' main universe multiverse restricted')} @@ -216,10 +199,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_replace(self): - """test_apt_src_replace - Test Autoreplacement of MIRROR and RELEASE in source specs with - Filename being set - """ + """Test Autoreplacement of MIRROR and RELEASE in source specs""" cfg = {'source': 'deb $MIRROR $RELEASE multiverse', 'filename': self.aptlistfile} self.apt_src_replacement(self.aptlistfile, [cfg]) @@ -245,10 +225,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_replace_tri(self): - """test_apt_src_replace_tri - Test three autoreplacements of MIRROR and RELEASE in source specs with - Filename being set - """ + """Test triple Autoreplacement of MIRROR and RELEASE in source specs""" cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', 'filename': self.aptlistfile} cfg2 = {'source': 'deb $MIRROR $RELEASE main', @@ -258,13 +235,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_replace_tri([cfg1, cfg2, cfg3]) def test_apt_src_replace_dict_tri(self): - """test_apt_src_replace_dict_tri - Test three autoreplacements of MIRROR and RELEASE in source specs with - Filename being set - Provided in a dictionary with filename being the key (new format) - We also test a new special conditions of the new format that allows - filenames to be overwritten inside the directory entry. - """ + """Test triple Autoreplacement in source specs (dict)""" cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, 'notused': {'source': 'deb $MIRROR $RELEASE main', 'filename': self.aptlistfile2}, @@ -272,10 +243,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_replace_tri(cfg) def test_apt_src_replace_nofn(self): - """test_apt_src_replace_nofn - Test Autoreplacement of MIRROR and RELEASE in source specs with - No filename being set - """ + """Test Autoreplacement of MIRROR and RELEASE in source specs nofile""" cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} with mock.patch.object(os.path, 'join', side_effect=self.myjoin): self.apt_src_replacement(self.fallbackfn, [cfg]) @@ -307,9 +275,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_keyid(self): - """test_apt_src_keyid - Test specification of a source + keyid with filename being set - """ + """Test specification of a source + keyid with filename being set""" cfg = {'source': ('deb ' 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' @@ -319,10 +285,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_keyid(self.aptlistfile, [cfg], 1) def test_apt_src_keyid_tri(self): - """test_apt_src_keyid_tri - Test specification of a source + keyid with filename being set - Setting three of such, check for content and keys - """ + """Test 3x specification of a source + keyid with filename being set""" cfg1 = {'source': ('deb ' 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' @@ -359,9 +322,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_keyid_nofn(self): - """test_apt_src_keyid_nofn - Test specification of a source + keyid without filename being set - """ + """Test specification of a source + keyid without filename being set""" cfg = {'source': ('deb ' 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' @@ -392,9 +353,7 @@ class TestAptSourceConfig(TestCase): contents, flags=re.IGNORECASE)) def test_apt_src_key(self): - """test_apt_src_key - Test specification of a source + key with filename being set - """ + """Test specification of a source + key with filename being set""" cfg = {'source': ('deb ' 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' @@ -404,9 +363,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_key(self.aptlistfile, cfg) def test_apt_src_key_nofn(self): - """test_apt_src_key_nofn - Test specification of a source + key without filename being set - """ + """Test specification of a source + key without filename being set""" cfg = {'source': ('deb ' 'http://ppa.launchpad.net/' 'smoser/cloud-init-test/ubuntu' @@ -416,9 +373,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_key(self.fallbackfn, cfg) def test_apt_src_keyonly(self): - """test_apt_src_keyonly - Test specification key without source - """ + """Test specifying key without source""" params = self._get_default_params() cfg = {'key': "fakekey 4242", 'filename': self.aptlistfile} @@ -433,9 +388,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_keyidonly(self): - """test_apt_src_keyidonly - Test specification of a keyid without source - """ + """Test specification of a keyid without source""" params = self._get_default_params() cfg = {'keyid': "03683F77", 'filename': self.aptlistfile} @@ -509,9 +462,7 @@ class TestAptSourceConfig(TestCase): self.apt_src_keyid_real(cfg, EXPECTEDKEY) def test_apt_src_ppa(self): - """test_apt_src_ppa - Test specification of a ppa - """ + """Test adding a ppa""" params = self._get_default_params() cfg = {'source': 'ppa:smoser/cloud-init-test', 'filename': self.aptlistfile} @@ -529,9 +480,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_ppa_tri(self): - """test_apt_src_ppa_tri - Test specification of a ppa - """ + """Test adding three ppa's""" params = self._get_default_params() cfg1 = {'source': 'ppa:smoser/cloud-init-test', 'filename': self.aptlistfile} @@ -557,10 +506,7 @@ class TestAptSourceConfig(TestCase): self.assertFalse(os.path.isfile(self.aptlistfile3)) def test_convert_to_new_format(self): - """test_convert_to_new_format - Test the conversion of old to new format - And the noop conversion of new to new format as well - """ + """Test the conversion of old to new format""" cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', 'filename': self.aptlistfile} cfg2 = {'source': 'deb $MIRROR $RELEASE main', -- cgit v1.2.3 From 1f05fd90f38b8ff1f1b2d6418030545d502b3965 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 7 Jun 2016 08:23:14 +0200 Subject: drop gpg activity from aot-source key tests While the unittest gets a bit less real by that change, it will work in protected environment (e.g. sbuild) and leave the developers .gpg keyring alone. --- .../test_handler/test_handler_apt_source.py | 24 +++++----------------- 1 file changed, 5 insertions(+), 19 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 1f76e770..6223674b 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -4,7 +4,6 @@ Testing various config variations of the apt_source config import os import re import shutil -import socket import tempfile try: @@ -410,27 +409,14 @@ class TestAptSourceConfig(TestCase): """ params = self._get_default_params() - def fake_gpg_recv_key(key, keyserver): - """try original gpg_recv_key, but allow fall back""" - try: - self.orig_gpg_recv_key(key, keyserver) - except ValueError: - # if this is a networking issue mock it's effect - testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - testsock.connect((keyserver, 80)) - testsock.close() - except socket.error: - # as fallback add the known key as a working recv would - util.subp(("gpg", "--import", "-"), EXPECTEDKEY, - capture=True) - with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(util, 'gpg_recv_key', - side_effect=fake_gpg_recv_key): + with mock.patch.object(util, 'getkeybyid', + return_value=expectedkey) as mockgetkey: cc_apt_configure.add_apt_sources([cfg], params) - # no matter if really imported or faked, ensure we add the right key + mockgetkey.assert_called_with(cfg['keyid'], + cfg.get('keyserver', + 'keyserver.ubuntu.com')) mockkey.assert_called_with(expectedkey) # filename should be ignored on key only -- cgit v1.2.3 From 3f4d7d4f1fd946b8b6c6c490cb284a58eb6f695e Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 7 Jun 2016 09:09:58 +0200 Subject: harden mirrorfail tests for the fact that even good mirrors can fail This might happen e.g. in locked down build environments. In those cases this is detected and the test skipped while not giving up testing it in more capable environments. --- .../test_handler_apt_configure_sources_list.py | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 20e61995..6e123af7 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -5,6 +5,24 @@ import logging import os import shutil import tempfile +import socket + +# on SkipTest: +# - unittest SkipTest is first preference, but it's only available +# for >= 2.7 +# - unittest2 SkipTest is second preference for older pythons. This +# mirrors logic for choosing SkipTest exception in testtools +# - if none of the above, provide custom class +try: + from unittest.case import SkipTest +except ImportError: + try: + from unittest2.case import SkipTest + except ImportError: + class SkipTest(Exception): + """Raise this exception to mark a test as skipped. + """ + pass try: from unittest import mock @@ -126,14 +144,28 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """Test rendering of a source.list from template for ubuntu""" self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') + @staticmethod + def check_connectivity(target): + """try original gpg_recv_key, but allow fall back""" + testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + testsock.settimeout(10) + try: + testsock.connect((target, 80)) + testsock.close() + except socket.error: + raise SkipTest("Test skipped: no network connectivity to %s" + % target) + def test_apt_srcl_debian_mirrorfail(self): """Test rendering of a source.list from template for debian""" + self.check_connectivity('httpredir.debian.org') self.apt_source_list('debian', ['http://does.not.exist', 'http://httpredir.debian.org/debian'], 'http://httpredir.debian.org/debian') def test_apt_srcl_ubuntu_mirrorfail(self): """Test rendering of a source.list from template for ubuntu""" + self.check_connectivity('archive.ubuntu.com') self.apt_source_list('ubuntu', ['http://does.not.exist', 'http://archive.ubuntu.com/ubuntu/'], 'http://archive.ubuntu.com/ubuntu/') -- cgit v1.2.3 From cea03ed6bc71d9efe77f296662817d766458e3ac Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 7 Jun 2016 10:55:14 +0200 Subject: mock get_primary_arch for TestAptSourceConfigSourceList for CentOS This allows the unittest to pass on CentOS, before it failed as it had no dpkg available. --- .../test_handler/test_handler_apt_configure_sources_list.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 6e123af7..c3043b14 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -38,6 +38,8 @@ from cloudinit import util from cloudinit.config import cc_apt_configure from cloudinit.sources import DataSourceNone +from cloudinit.distros.debian import Distro + from .. import helpers as t_help LOG = logging.getLogger(__name__) @@ -87,8 +89,6 @@ def load_tfile_or_url(*args, **kwargs): return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) -# This feature is apt specific and thereby is disabled in environments without -@t_help.skipIf(not os.path.isfile(BIN_APT), "no apt") class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """TestAptSourceConfigSourceList Main Class to test sources list rendering @@ -180,8 +180,10 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): with mock.patch.object(util, 'subp', self.subp): with mock.patch.object(cc_apt_configure, 'get_release', return_value='fakerelease'): - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) + with mock.patch.object(Distro, 'get_primary_arch', + return_value='amd64'): + cc_apt_configure.handle("notimportant", cfg, mycloud, + LOG, None) mockwrite.assert_called_once_with( '/etc/apt/sources.list', -- cgit v1.2.3 From f4df4fbf200ef04c841bc36baa559bce265fcb38 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Tue, 7 Jun 2016 10:58:38 +0200 Subject: enable test_handler_apt_source unittests on CentOS Tested and working on CentOS7 (container), so we can enable the tests --- tests/unittests/test_handler/test_handler_apt_source.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 6223674b..3f87fad3 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -16,7 +16,6 @@ from cloudinit.config import cc_apt_configure from cloudinit import util from ..helpers import TestCase -from .. import helpers as t_help BIN_APT = "/usr/bin/apt" @@ -42,8 +41,6 @@ def load_tfile_or_url(*args, **kwargs): return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) -# This feature is apt specific and thereby is disabled in environments without -@t_help.skipIf(not os.path.isfile(BIN_APT), "no apt") class TestAptSourceConfig(TestCase): """TestAptSourceConfig Main Class to test apt_source configs -- cgit v1.2.3 From b89ac6b7caeeade5ad21137773ac4496cdaea2c5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 09:18:35 +0200 Subject: move gpg functions into gpg.py This helps for cleaner code structuring. ALong that makeing sure all these functions have a gpg_prefix. --- cloudinit/config/cc_apt_configure.py | 3 +- cloudinit/gpg.py | 64 ++++++++++++++++++++++ cloudinit/util.py | 38 ------------- .../test_handler/test_handler_apt_source.py | 4 +- 4 files changed, 68 insertions(+), 41 deletions(-) create mode 100644 cloudinit/gpg.py (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index be6324a2..ba080930 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -24,6 +24,7 @@ import re from cloudinit import templater from cloudinit import util +from cloudinit import gpg distros = ['ubuntu', 'debian'] @@ -167,7 +168,7 @@ def add_apt_key(ent): keyserver = "keyserver.ubuntu.com" if 'keyserver' in ent: keyserver = ent['keyserver'] - ent['key'] = util.getkeybyid(ent['keyid'], keyserver) + ent['key'] = gpg.gpg_getkeybyid(ent['keyid'], keyserver) if 'key' in ent: add_apt_key_raw(ent['key']) diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py new file mode 100644 index 00000000..620dfb19 --- /dev/null +++ b/cloudinit/gpg.py @@ -0,0 +1,64 @@ +"""gpg.py - Collection of gpg key related functions""" +# vi: ts=4 expandtab +# +# Copyright (C) 2016 Canonical Ltd. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# Author: Joshua Harlow +# Author: Christian Ehrhardt +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from cloudinit import util +from cloudinit import log as logging + +LOG = logging.getLogger(__name__) + + +def gpg_export_armour(key): + """Export gpg key, armoured key gets returned""" + (armour, _) = util.subp(["gpg", "--export", "--armour", key], capture=True) + return armour + + +def gpg_recv_key(key, keyserver): + """Receive gpg key from the specified keyserver""" + try: + util.subp(["gpg", "--keyserver", keyserver, "--recv", key], + capture=True) + except util.ProcessExecutionError as error: + raise ValueError('Failed to import key %s from server %s - error %s' % + (key, keyserver, error)) + + +def gpg_delete_key(key): + """Delete the specified key from the local gpg ring""" + util.subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) + + +def gpg_getkeybyid(keyid, keyserver): + """get gpg keyid from keyserver""" + armour = gpg_export_armour(keyid) + if not armour: + try: + gpg_recv_key(keyid, keyserver=keyserver) + except ValueError: + LOG.exception('Failed to obtain gpg key %s', keyid) + raise + + armour = gpg_export_armour(keyid) + # delete just imported key to leave environment as it was before + gpg_delete_key(keyid) + + return armour.rstrip('\n') diff --git a/cloudinit/util.py b/cloudinit/util.py index 6d16532d..d6b80dbe 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2234,41 +2234,3 @@ def message_from_string(string): if sys.version_info[:2] < (2, 7): return email.message_from_file(six.StringIO(string)) return email.message_from_string(string) - - -def gpg_export_armour(key): - """Export gpg key, armoured key gets returned""" - (armour, _) = subp(["gpg", "--export", "--armour", key], capture=True) - return armour - - -def gpg_recv_key(key, keyserver): - """Receive gpg key from the specified keyserver""" - try: - subp(["gpg", "--keyserver", keyserver, "--recv", key], - capture=True) - except ProcessExecutionError as error: - raise ValueError('Failed to import key %s from server %s - error %s' % - (key, keyserver, error)) - - -def gpg_delete_key(key): - """Delete the specified key from the local gpg ring""" - subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) - - -def getkeybyid(keyid, keyserver): - """get gpg keyid from keyserver""" - armour = gpg_export_armour(keyid) - if not armour: - try: - gpg_recv_key(keyid, keyserver=keyserver) - except ValueError: - LOG.exception('Failed to obtain gpg key %s', keyid) - raise - - armour = gpg_export_armour(keyid) - # delete just imported key to leave environment as it was before - gpg_delete_key(keyid) - - return armour.rstrip('\n') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 3f87fad3..b09dd8ab 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -14,6 +14,7 @@ from mock import call from cloudinit.config import cc_apt_configure from cloudinit import util +from cloudinit import gpg from ..helpers import TestCase @@ -58,7 +59,6 @@ class TestAptSourceConfig(TestCase): # mock fallback filename into writable tmp dir self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") - self.orig_gpg_recv_key = util.gpg_recv_key patcher = mock.patch("cloudinit.config.cc_apt_configure.get_release") get_rel = patcher.start() @@ -407,7 +407,7 @@ class TestAptSourceConfig(TestCase): params = self._get_default_params() with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(util, 'getkeybyid', + with mock.patch.object(gpg, 'gpg_getkeybyid', return_value=expectedkey) as mockgetkey: cc_apt_configure.add_apt_sources([cfg], params) -- cgit v1.2.3 From ab5fcc807f48eefa032c8a07ff79e9e5bd6b93a6 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 09:40:32 +0200 Subject: move SkipTest definition to tests/unittests/helpers.py to be reusable --- tests/unittests/helpers.py | 17 +++++++++++++++++ .../test_handler_apt_configure_sources_list.py | 21 ++------------------- 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'tests') diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 50b2bd72..59361215 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -344,3 +344,20 @@ except AttributeError: print(reason, file=sys.stderr) return wrapper return decorator + +# on SkipTest: +# - unittest SkipTest is first preference, but it's only available +# for >= 2.7 +# - unittest2 SkipTest is second preference for older pythons. This +# mirrors logic for choosing SkipTest exception in testtools +# - if none of the above, provide custom class +try: + from unittest.case import SkipTest +except ImportError: + try: + from unittest2.case import SkipTest + except ImportError: + class SkipTest(Exception): + """Raise this exception to mark a test as skipped. + """ + pass diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index c3043b14..2e604216 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -7,23 +7,6 @@ import shutil import tempfile import socket -# on SkipTest: -# - unittest SkipTest is first preference, but it's only available -# for >= 2.7 -# - unittest2 SkipTest is second preference for older pythons. This -# mirrors logic for choosing SkipTest exception in testtools -# - if none of the above, provide custom class -try: - from unittest.case import SkipTest -except ImportError: - try: - from unittest2.case import SkipTest - except ImportError: - class SkipTest(Exception): - """Raise this exception to mark a test as skipped. - """ - pass - try: from unittest import mock except ImportError: @@ -153,8 +136,8 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): testsock.connect((target, 80)) testsock.close() except socket.error: - raise SkipTest("Test skipped: no network connectivity to %s" - % target) + raise t_help.SkipTest("Test skipped: no network connectivity to %s" + % target) def test_apt_srcl_debian_mirrorfail(self): """Test rendering of a source.list from template for debian""" -- cgit v1.2.3 From 2d7f4673db55d5111d5e31e32eea3ca64c4e5f79 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 10:01:32 +0200 Subject: remove unused BIN_APT constant --- tests/unittests/test_handler/test_handler_apt_configure_sources_list.py | 2 -- tests/unittests/test_handler/test_handler_apt_source.py | 2 -- 2 files changed, 4 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 2e604216..bedef51c 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -27,8 +27,6 @@ from .. import helpers as t_help LOG = logging.getLogger(__name__) -BIN_APT = "/usr/bin/apt" - YAML_TEXT_CUSTOM_SL = """ apt_mirror: http://archive.ubuntu.com/ubuntu/ apt_custom_sources_list: | diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index b09dd8ab..5b52f709 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -18,8 +18,6 @@ from cloudinit import gpg from ..helpers import TestCase -BIN_APT = "/usr/bin/apt" - EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 -- cgit v1.2.3 From 108511d93b336e0b8e0807fbe876bad4cc07277f Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 10:01:57 +0200 Subject: fix docstring for check connectivity --- tests/unittests/test_handler/test_handler_apt_configure_sources_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index bedef51c..83e2e0cc 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -127,7 +127,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): @staticmethod def check_connectivity(target): - """try original gpg_recv_key, but allow fall back""" + """Check for required connectivity, if not skip this test""" testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) testsock.settimeout(10) try: -- cgit v1.2.3 From 0e734b63c64c5534813d7647d33870c9fc3d3a0c Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 10:35:39 +0200 Subject: mock is_resolvable in mirrorfail tests to remove dependency to external net --- tests/unittests/helpers.py | 17 --------- .../test_handler_apt_configure_sources_list.py | 43 ++++++++++++---------- 2 files changed, 24 insertions(+), 36 deletions(-) (limited to 'tests') diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 59361215..50b2bd72 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -344,20 +344,3 @@ except AttributeError: print(reason, file=sys.stderr) return wrapper return decorator - -# on SkipTest: -# - unittest SkipTest is first preference, but it's only available -# for >= 2.7 -# - unittest2 SkipTest is second preference for older pythons. This -# mirrors logic for choosing SkipTest exception in testtools -# - if none of the above, provide custom class -try: - from unittest.case import SkipTest -except ImportError: - try: - from unittest2.case import SkipTest - except ImportError: - class SkipTest(Exception): - """Raise this exception to mark a test as skipped. - """ - pass diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py index 83e2e0cc..acde0863 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py @@ -5,7 +5,6 @@ import logging import os import shutil import tempfile -import socket try: from unittest import mock @@ -126,30 +125,36 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') @staticmethod - def check_connectivity(target): - """Check for required connectivity, if not skip this test""" - testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - testsock.settimeout(10) - try: - testsock.connect((target, 80)) - testsock.close() - except socket.error: - raise t_help.SkipTest("Test skipped: no network connectivity to %s" - % target) + def myresolve(name): + """Fake util.is_resolvable for mirrorfail tests""" + if name == "does.not.exist": + print("Faking FAIL for '%s'" % name) + return False + else: + print("Faking SUCCESS for '%s'" % name) + return True def test_apt_srcl_debian_mirrorfail(self): """Test rendering of a source.list from template for debian""" - self.check_connectivity('httpredir.debian.org') - self.apt_source_list('debian', ['http://does.not.exist', - 'http://httpredir.debian.org/debian'], - 'http://httpredir.debian.org/debian') + with mock.patch.object(util, 'is_resolvable', + side_effect=self.myresolve) as mockresolve: + self.apt_source_list('debian', + ['http://does.not.exist', + 'http://httpredir.debian.org/debian'], + 'http://httpredir.debian.org/debian') + mockresolve.assert_any_call("does.not.exist") + mockresolve.assert_any_call("httpredir.debian.org") def test_apt_srcl_ubuntu_mirrorfail(self): """Test rendering of a source.list from template for ubuntu""" - self.check_connectivity('archive.ubuntu.com') - self.apt_source_list('ubuntu', ['http://does.not.exist', - 'http://archive.ubuntu.com/ubuntu/'], - 'http://archive.ubuntu.com/ubuntu/') + with mock.patch.object(util, 'is_resolvable', + side_effect=self.myresolve) as mockresolve: + self.apt_source_list('ubuntu', + ['http://does.not.exist', + 'http://archive.ubuntu.com/ubuntu/'], + 'http://archive.ubuntu.com/ubuntu/') + mockresolve.assert_any_call("does.not.exist") + mockresolve.assert_any_call("archive.ubuntu.com") def test_apt_srcl_custom(self): """Test rendering from a custom source.list template""" -- cgit v1.2.3 From 7e527b1b2f3fda558fb0f3a6958c42dde4716079 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Jun 2016 13:22:59 -0400 Subject: minor changes prior to merge a.) remove 'gpg_' from function names in new gpg module. b.) use --recv-keys rather than --recv --recv-keys is more obvious and works back to precise at least. c.) do not trim trailing '\n' from a armour'd key. --- cloudinit/config/cc_apt_configure.py | 4 ++-- cloudinit/gpg.py | 24 ++++++++++------------ .../test_handler/test_handler_apt_source.py | 4 ++-- 3 files changed, 15 insertions(+), 17 deletions(-) (limited to 'tests') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index ba080930..96c4a43d 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -22,9 +22,9 @@ import glob import os import re +from cloudinit import gpg from cloudinit import templater from cloudinit import util -from cloudinit import gpg distros = ['ubuntu', 'debian'] @@ -168,7 +168,7 @@ def add_apt_key(ent): keyserver = "keyserver.ubuntu.com" if 'keyserver' in ent: keyserver = ent['keyserver'] - ent['key'] = gpg.gpg_getkeybyid(ent['keyid'], keyserver) + ent['key'] = gpg.get_key_by_id(ent['keyid'], keyserver) if 'key' in ent: add_apt_key_raw(ent['key']) diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py index baa8b534..6a76d785 100644 --- a/cloudinit/gpg.py +++ b/cloudinit/gpg.py @@ -4,8 +4,6 @@ # Copyright (C) 2016 Canonical Ltd. # # Author: Scott Moser -# Author: Juerg Haefliger -# Author: Joshua Harlow # Author: Christian Ehrhardt # # This program is free software: you can redistribute it and/or modify @@ -20,13 +18,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from cloudinit import util from cloudinit import log as logging +from cloudinit import util LOG = logging.getLogger(__name__) -def gpg_export_armour(key): +def export_armour(key): """Export gpg key, armoured key gets returned""" try: (armour, _) = util.subp(["gpg", "--export", "--armour", key], @@ -38,11 +36,11 @@ def gpg_export_armour(key): return armour -def gpg_recv_key(key, keyserver): +def receive_key(key, keyserver): """Receive gpg key from the specified keyserver""" LOG.debug('Receive gpg key "%s"', key) try: - util.subp(["gpg", "--keyserver", keyserver, "--recv", key], + util.subp(["gpg", "--keyserver", keyserver, "--recv-keys", key], capture=True) except util.ProcessExecutionError as error: raise ValueError(('Failed to import key "%s" ' @@ -50,7 +48,7 @@ def gpg_recv_key(key, keyserver): (key, keyserver, error)) -def gpg_delete_key(key): +def delete_key(key): """Delete the specified key from the local gpg ring""" try: util.subp(["gpg", "--batch", "--yes", "--delete-keys", key], @@ -59,18 +57,18 @@ def gpg_delete_key(key): LOG.warn('Failed delete key "%s": %s', key, error) -def gpg_getkeybyid(keyid, keyserver): +def get_key_by_id(keyid, keyserver="keyserver.ubuntu.com"): """get gpg keyid from keyserver""" - armour = gpg_export_armour(keyid) + armour = export_armour(keyid) if not armour: try: - gpg_recv_key(keyid, keyserver=keyserver) - armour = gpg_export_armour(keyid) + receive_key(keyid, keyserver=keyserver) + armour = export_armour(keyid) except ValueError: LOG.exception('Failed to obtain gpg key %s', keyid) raise finally: # delete just imported key to leave environment as it was before - gpg_delete_key(keyid) + delete_key(keyid) - return armour.rstrip('\n') + return armour diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 5b52f709..99a4d860 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -13,8 +13,8 @@ except ImportError: from mock import call from cloudinit.config import cc_apt_configure -from cloudinit import util from cloudinit import gpg +from cloudinit import util from ..helpers import TestCase @@ -405,7 +405,7 @@ class TestAptSourceConfig(TestCase): params = self._get_default_params() with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(gpg, 'gpg_getkeybyid', + with mock.patch.object(gpg, 'get_key_by_id', return_value=expectedkey) as mockgetkey: cc_apt_configure.add_apt_sources([cfg], params) -- cgit v1.2.3 From cb74876aa4c9e81eab4f1f0fa4967988f708db15 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Jun 2016 14:49:34 -0400 Subject: fix one large source of leaked tmpfiles --- tests/unittests/test_datasource/test_smartos.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py index c809117b..ec57cff1 100644 --- a/tests/unittests/test_datasource/test_smartos.py +++ b/tests/unittests/test_datasource/test_smartos.py @@ -146,7 +146,9 @@ class TestSmartOSDataSource(FilesystemMockingTestCase): self.addCleanup(shutil.rmtree, self.tmp) self.paths = c_helpers.Paths({'cloud_dir': self.tmp}) - self.legacy_user_d = tempfile.mkdtemp() + self.legacy_user_d = os.path.join(self.tmp, 'legacy_user_tmp') + os.mkdir(self.legacy_user_d) + self.orig_lud = DataSourceSmartOS.LEGACY_USER_D DataSourceSmartOS.LEGACY_USER_D = self.legacy_user_d -- cgit v1.2.3 From 2d3eb3a5730208b84e141f08df10f23c1447fdb5 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Jun 2016 15:00:52 -0400 Subject: one more --- tests/unittests/test_datasource/test_azure_helper.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'tests') diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py index d07a1f07..9a15d15e 100644 --- a/tests/unittests/test_datasource/test_azure_helper.py +++ b/tests/unittests/test_datasource/test_azure_helper.py @@ -1,4 +1,5 @@ import os +import shutil from cloudinit.sources.helpers import azure as azure_helper @@ -288,6 +289,7 @@ class TestOpenSSLManager(TestCase): self.subp.side_effect = capture_directory manager = azure_helper.OpenSSLManager() self.assertEqual(manager.tmpdir, subp_directory['path']) + manager.clean_up() @mock.patch.object(azure_helper, 'cd', mock.MagicMock()) @mock.patch.object(azure_helper.tempfile, 'mkdtemp', mock.MagicMock()) -- cgit v1.2.3 From 785fe5632d58633dea5d6b2c1c5ecbaca35471a3 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Jun 2016 15:03:24 -0400 Subject: remove unused import --- tests/unittests/test_datasource/test_azure_helper.py | 1 - 1 file changed, 1 deletion(-) (limited to 'tests') diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py index 9a15d15e..70e8f7f2 100644 --- a/tests/unittests/test_datasource/test_azure_helper.py +++ b/tests/unittests/test_datasource/test_azure_helper.py @@ -1,5 +1,4 @@ import os -import shutil from cloudinit.sources.helpers import azure as azure_helper -- cgit v1.2.3 From 30f1a6be14635aadf7c0c0b7071ddf947563a1b3 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 10 Jun 2016 13:51:45 -0700 Subject: Just do all the imports on one line --- tests/unittests/test_datasource/test_azure_helper.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py index d7037830..49227b53 100644 --- a/tests/unittests/test_datasource/test_azure_helper.py +++ b/tests/unittests/test_datasource/test_azure_helper.py @@ -2,10 +2,7 @@ import os from cloudinit.sources.helpers import azure as azure_helper -from ..helpers import ExitStack -from ..helpers import TestCase - -from ..helpers import mock +from ..helpers import ExitStack, mock, TestCase GOAL_STATE_TEMPLATE = """\ -- cgit v1.2.3 From 6ada153ebfd9483d76f904dafaa1fc80f61c9205 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 10 Jun 2016 14:16:51 -0700 Subject: Just mock 'on_first_boot' vs special argument --- cloudinit/sources/DataSourceConfigDrive.py | 5 ++--- tests/unittests/test_datasource/test_configdrive.py | 11 +++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py index cea08de2..3130e618 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -62,7 +62,7 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource): mstr += "[source=%s]" % (self.source) return mstr - def get_data(self, skip_first_boot=False): + def get_data(self): found = None md = {} results = {} @@ -111,8 +111,7 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource): # 'injected files' and apply legacy ENI network format. prev_iid = get_previous_iid(self.paths) cur_iid = md['instance-id'] - if (prev_iid != cur_iid and self.dsmode == sources.DSMODE_PASS - and not skip_first_boot): + if prev_iid != cur_iid and self.dsmode == sources.DSMODE_PASS: on_first_boot(results, distro=self.distro) LOG.debug("%s: not claiming datasource, dsmode=%s", self, self.dsmode) diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index b898e2bd..18551b92 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -370,7 +370,8 @@ class TestConfigDriveDataSource(TestCase): util.find_devs_with = orig_find_devs_with util.is_partition = orig_is_partition - def test_pubkeys_v2(self): + @mock.patch('cloudinit.sources.DataSourceConfigDrive.on_first_boot') + def test_pubkeys_v2(self, on_first_boot): """Verify that public-keys work in config-drive-v2.""" populate_dir(self.tmp, CFG_DRIVE_FILES_V2) myds = cfg_ds_from_dir(self.tmp) @@ -385,13 +386,15 @@ class TestNetJson(TestCase): self.addCleanup(shutil.rmtree, self.tmp) self.maxDiff = None - def test_network_data_is_found(self): + @mock.patch('cloudinit.sources.DataSourceConfigDrive.on_first_boot') + def test_network_data_is_found(self, on_first_boot): """Verify that network_data is present in ds in config-drive-v2.""" populate_dir(self.tmp, CFG_DRIVE_FILES_V2) myds = cfg_ds_from_dir(self.tmp) self.assertIsNotNone(myds.network_json) - def test_network_config_is_converted(self): + @mock.patch('cloudinit.sources.DataSourceConfigDrive.on_first_boot') + def test_network_config_is_converted(self, on_first_boot): """Verify that network_data is converted and present on ds object.""" populate_dir(self.tmp, CFG_DRIVE_FILES_V2) myds = cfg_ds_from_dir(self.tmp) @@ -558,7 +561,7 @@ def cfg_ds_from_dir(seed_d): helpers.Paths({})) cfg_ds.seed_dir = seed_d cfg_ds.known_macs = KNOWN_MACS.copy() - if not cfg_ds.get_data(skip_first_boot=True): + if not cfg_ds.get_data(): raise RuntimeError("Data source did not extract itself from" " seed directory %s" % seed_d) return cfg_ds -- cgit v1.2.3 From cfcac2a70bc4dd9b47358261750ff535e78f4d5f Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 10 Jun 2016 15:02:21 -0700 Subject: Add a sysconfig rendering test --- cloudinit/net/sysconfig.py | 9 +++-- tests/unittests/test_net.py | 93 +++++++++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 28 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index ff7e467f..01770213 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -102,7 +102,7 @@ class ConfigMap(object): return len(self._conf) def to_string(self): - buf = compat.StringIO() + buf = six.StringIO() buf.write(self.default_header) if self._conf: buf.write("\n") @@ -110,7 +110,7 @@ class ConfigMap(object): value = self._conf[key] if isinstance(value, bool): value = self._bool_map[value] - if not isinstance(value, compat.string_types): + if not isinstance(value, six.string_types): value = str(value) buf.write("%s=%s\n" % (key, _quote_value(value))) return buf.getvalue() @@ -229,7 +229,7 @@ class Renderer(object): """Given state, emit udev rules to map mac to ifname.""" # TODO(harlowja): this seems shared between eni renderer and # this, so move it to a shared location. - content = compat.StringIO() + content = six.StringIO() for iface in network_state.iter_interfaces(_filter_by_physical): # for physical interfaces write out a persist net udev rule if 'name' in iface and iface.get('mac_address'): @@ -253,7 +253,7 @@ class Renderer(object): iface_cfg['DHCPV6C'] = True iface_cfg['IPV6INIT'] = True iface_cfg['BOOTPROTO'] = 'dhcp' - elif subnet_type == 'dhcp4': + elif subnet_type in ['dhcp4', 'dhcp']: iface_cfg['BOOTPROTO'] = 'dhcp' elif subnet_type == 'static': iface_cfg['BOOTPROTO'] = 'static' @@ -414,6 +414,7 @@ class Renderer(object): self, target, network_state, sysconf_dir="etc/sysconfig/", netrules='etc/udev/rules.d/70-persistent-net.rules', dns='etc/resolv.conf'): + network_state = NetworkStateHelper(network_state) if target: base_sysconf_dir = os.path.join(target, sysconf_dir) else: diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 7998111a..e899c749 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -2,6 +2,7 @@ from cloudinit import net from cloudinit.net import cmdline from cloudinit.net import eni from cloudinit.net import network_state +from cloudinit.net import sysconfig from cloudinit import util from .helpers import mock @@ -75,7 +76,36 @@ STATIC_EXPECTED_1 = { } -class TestEniNetRendering(TestCase): +def _setup_test(tmp_dir, mock_get_devicelist, mock_sys_netdev_info, + mock_sys_dev_path): + mock_get_devicelist.return_value = ['eth1000'] + dev_characteristics = { + 'eth1000': { + "bridge": False, + "carrier": False, + "dormant": False, + "operstate": "down", + "address": "07-1C-C6-75-A4-BE", + } + } + + def netdev_info(name, field): + return dev_characteristics[name][field] + + mock_sys_netdev_info.side_effect = netdev_info + + def sys_dev_path(devname, path=""): + return tmp_dir + devname + "/" + path + + for dev in dev_characteristics: + os.makedirs(os.path.join(tmp_dir, dev)) + with open(os.path.join(tmp_dir, dev, 'operstate'), 'w') as fh: + fh.write("down") + + mock_sys_dev_path.side_effect = sys_dev_path + + +class TestSysConfigRendering(TestCase): @mock.patch("cloudinit.net.sys_dev_path") @mock.patch("cloudinit.net.sys_netdev_info") @@ -83,35 +113,50 @@ class TestEniNetRendering(TestCase): def test_default_generation(self, mock_get_devicelist, mock_sys_netdev_info, mock_sys_dev_path): - mock_get_devicelist.return_value = ['eth1000', 'lo'] - - dev_characteristics = { - 'eth1000': { - "bridge": False, - "carrier": False, - "dormant": False, - "operstate": "down", - "address": "07-1C-C6-75-A4-BE", - } - } + tmp_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, tmp_dir) + _setup_test(tmp_dir, mock_get_devicelist, + mock_sys_netdev_info, mock_sys_dev_path) - def netdev_info(name, field): - return dev_characteristics[name][field] + network_cfg = net.generate_fallback_config() + ns = network_state.parse_net_config_data(network_cfg, + skip_broken=False) - mock_sys_netdev_info.side_effect = netdev_info + render_dir = os.path.join(tmp_dir, "render") + os.makedirs(render_dir) - tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, tmp_dir) + renderer = sysconfig.Renderer() + renderer.render_network_state(render_dir, ns) + + render_file = 'etc/sysconfig/network-scripts/ifcfg-eth1000' + with open(os.path.join(render_dir, render_file)) as fh: + content = fh.read() + expected_content = """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=dhcp +DEVICE=eth1000 +HWADDR=07-1C-C6-75-A4-BE +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip() + self.assertEqual(expected_content, content) - def sys_dev_path(devname, path=""): - return tmp_dir + devname + "/" + path - for dev in dev_characteristics: - os.makedirs(os.path.join(tmp_dir, dev)) - with open(os.path.join(tmp_dir, dev, 'operstate'), 'w') as fh: - fh.write("down") +class TestEniNetRendering(TestCase): - mock_sys_dev_path.side_effect = sys_dev_path + @mock.patch("cloudinit.net.sys_dev_path") + @mock.patch("cloudinit.net.sys_netdev_info") + @mock.patch("cloudinit.net.get_devicelist") + def test_default_generation(self, mock_get_devicelist, + mock_sys_netdev_info, + mock_sys_dev_path): + tmp_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, tmp_dir) + _setup_test(tmp_dir, mock_get_devicelist, + mock_sys_netdev_info, mock_sys_dev_path) network_cfg = net.generate_fallback_config() ns = network_state.parse_net_config_data(network_cfg, -- cgit v1.2.3 From f666f6116be64cb1c033ed6d0f829bdb2f21ba69 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 10 Jun 2016 15:23:32 -0700 Subject: Add a bunch more sample tests for sysconfig --- cloudinit/net/sysconfig.py | 20 +++++++++--- tests/unittests/test_net.py | 80 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 01770213..a4bcaaad 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -24,6 +24,19 @@ from . import network_state from .udev import generate_udev_rule +def _make_header(sep='#'): + lines = [ + "Created by cloud-init on instance boot automatically, do not edit.", + "", + ] + for i in range(0, len(lines)): + if lines[i]: + lines[i] = sep + " " + lines[i] + else: + lines[i] = sep + return "\n".join(lines) + + def _filter_by_type(match_type): return lambda iface: match_type == iface['type'] @@ -80,9 +93,6 @@ class NetworkStateHelper(object): class ConfigMap(object): """Sysconfig like dictionary object.""" - default_header = ('# Created by cloud-init on instance' - ' boot automatically, do not edit.\n#') - # Why does redhat prefer yes/no to true/false?? _bool_map = { True: 'yes', @@ -103,7 +113,7 @@ class ConfigMap(object): def to_string(self): buf = six.StringIO() - buf.write(self.default_header) + buf.write(_make_header()) if self._conf: buf.write("\n") for key in sorted(self._conf.keys()): @@ -365,7 +375,7 @@ class Renderer(object): content.add_nameserver(ns) for d in network_state.dns_searchdomains: content.add_search_domain(d) - return str(content) + return "\n".join([_make_header(';'), str(content)]) @classmethod def _render_bridge_interfaces(cls, network_state, iface_contents): diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index e899c749..4f4515a9 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -3,6 +3,7 @@ from cloudinit.net import cmdline from cloudinit.net import eni from cloudinit.net import network_state from cloudinit.net import sysconfig +from cloudinit.sources.helpers import openstack from cloudinit import util from .helpers import mock @@ -144,6 +145,85 @@ USERCTL=no """.lstrip() self.assertEqual(expected_content, content) + def test_openstack_rendering_sample(self): + ex_input = { + "services": [{"type": "dns", "address": "172.19.0.12"}], + "networks": [ + {"network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", + "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"}], + "links": [{"ethernet_mac_address": "fa:16:3e:ed:9a:59", + "mtu": None, "type": "bridge", "id": + "tap1a81968a-79", + "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f"}]} + ex_mac_addrs = { + 'fa:16:3e:ed:9a:59': 'eth0', + } + network_cfg = openstack.convert_net_json( + ex_input, known_macs=ex_mac_addrs) + ns = network_state.parse_net_config_data(network_cfg, + skip_broken=False) + + tmp_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, tmp_dir) + render_dir = os.path.join(tmp_dir, "render") + renderer = sysconfig.Renderer() + renderer.render_network_state(render_dir, ns) + + render_file = 'etc/sysconfig/network-scripts/ifcfg-eth0' + with open(os.path.join(render_dir, render_file)) as fh: + content = fh.read() + expected_content = """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=static +DEFROUTE=yes +DEVICE=eth0 +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() + self.assertEqual(expected_content, content) + + route_render_file = 'etc/sysconfig/network-scripts/route-eth0' + with open(os.path.join(render_dir, route_render_file)) as fh: + content = fh.read() + expected_content = """ +# Created by cloud-init on instance boot automatically, do not edit. +# +ADDRESS0=0.0.0.0 +GATEWAY0=172.19.3.254 +NETMASK0=0.0.0.0 +""".lstrip() + self.assertEqual(expected_content, content) + + resolv_render_file = 'etc/resolv.conf' + with open(os.path.join(render_dir, resolv_render_file)) as fh: + content = fh.read() + expected_content = """ +; Created by cloud-init on instance boot automatically, do not edit. +; +nameserver 172.19.0.12 +""".lstrip() + self.assertEqual(expected_content, content) + + rules_render_file = 'etc/udev/rules.d/70-persistent-net.rules' + with open(os.path.join(render_dir, rules_render_file)) as fh: + content = fh.read() + expected_content = "".join([ + 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']) + self.assertEqual(expected_content, content) + class TestEniNetRendering(TestCase): -- cgit v1.2.3 From e0b1e3a8c925446ee9e6d15aae1783b49fb870f8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 14 Jun 2016 10:58:18 -0700 Subject: Make the os samples easier to extend (for new samples) --- cloudinit/net/network_state.py | 1 + tests/unittests/test_net.py | 153 +++++++++++++++++++++-------------------- 2 files changed, 79 insertions(+), 75 deletions(-) (limited to 'tests') diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index d75fc852..8ca5106f 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -170,6 +170,7 @@ class NetworkStateInterpreter(object): self._version = version self._config = config self._network_state = copy.deepcopy(self.initial_network_state) + self._parsed = False @property def network_state(self): diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 4f4515a9..43602030 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -76,6 +76,71 @@ STATIC_EXPECTED_1 = { 'dns_nameservers': ['10.0.1.1']}], } +# Examples (and expected outputs for various renderers). +OS_SAMPLES = [ + { + 'in_data': { + "services": [{"type": "dns", "address": "172.19.0.12"}], + "networks": [{ + "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", + "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" + }], + "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=static +DEFROUTE=yes +DEVICE=eth0 +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/route-eth0', + """ +# Created by cloud-init on instance boot automatically, do not edit. +# +ADDRESS0=0.0.0.0 +GATEWAY0=172.19.3.254 +NETMASK0=0.0.0.0 +""".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']))] + } +] + def _setup_test(tmp_dir, mock_get_devicelist, mock_sys_netdev_info, mock_sys_dev_path): @@ -145,84 +210,22 @@ USERCTL=no """.lstrip() self.assertEqual(expected_content, content) - def test_openstack_rendering_sample(self): - ex_input = { - "services": [{"type": "dns", "address": "172.19.0.12"}], - "networks": [ - {"network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", - "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"}], - "links": [{"ethernet_mac_address": "fa:16:3e:ed:9a:59", - "mtu": None, "type": "bridge", "id": - "tap1a81968a-79", - "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f"}]} - ex_mac_addrs = { - 'fa:16:3e:ed:9a:59': 'eth0', - } - network_cfg = openstack.convert_net_json( - ex_input, known_macs=ex_mac_addrs) - ns = network_state.parse_net_config_data(network_cfg, - skip_broken=False) - + def test_openstack_rendering_samples(self): tmp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmp_dir) render_dir = os.path.join(tmp_dir, "render") - renderer = sysconfig.Renderer() - renderer.render_network_state(render_dir, ns) - - render_file = 'etc/sysconfig/network-scripts/ifcfg-eth0' - with open(os.path.join(render_dir, render_file)) as fh: - content = fh.read() - expected_content = """ -# Created by cloud-init on instance boot automatically, do not edit. -# -BOOTPROTO=static -DEFROUTE=yes -DEVICE=eth0 -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() - self.assertEqual(expected_content, content) - - route_render_file = 'etc/sysconfig/network-scripts/route-eth0' - with open(os.path.join(render_dir, route_render_file)) as fh: - content = fh.read() - expected_content = """ -# Created by cloud-init on instance boot automatically, do not edit. -# -ADDRESS0=0.0.0.0 -GATEWAY0=172.19.3.254 -NETMASK0=0.0.0.0 -""".lstrip() - self.assertEqual(expected_content, content) - - resolv_render_file = 'etc/resolv.conf' - with open(os.path.join(render_dir, resolv_render_file)) as fh: - content = fh.read() - expected_content = """ -; Created by cloud-init on instance boot automatically, do not edit. -; -nameserver 172.19.0.12 -""".lstrip() - self.assertEqual(expected_content, content) - - rules_render_file = 'etc/udev/rules.d/70-persistent-net.rules' - with open(os.path.join(render_dir, rules_render_file)) as fh: - content = fh.read() - expected_content = "".join([ - 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', - 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']) - self.assertEqual(expected_content, content) + for os_sample in OS_SAMPLES: + ex_input = os_sample['in_data'] + ex_mac_addrs = os_sample['in_macs'] + network_cfg = openstack.convert_net_json( + ex_input, known_macs=ex_mac_addrs) + ns = network_state.parse_net_config_data(network_cfg, + skip_broken=False) + renderer = sysconfig.Renderer() + renderer.render_network_state(render_dir, ns) + for fn, expected_content in os_sample.get('out_sysconfig', []): + with open(os.path.join(render_dir, fn)) as fh: + self.assertEqual(expected_content, fh.read()) class TestEniNetRendering(TestCase): -- cgit v1.2.3 From 93afde09d89e60c29dfd20790e30a06f031c82e1 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 14 Jun 2016 14:56:51 -0700 Subject: Make the bin/cloud-init an actual console entrypoint This allows for the test_cli test to be more sane. --- bin/cloud-init | 689 -------------------------------------------- cloudinit/cmd/__init__.py | 21 ++ cloudinit/cmd/main.py | 682 +++++++++++++++++++++++++++++++++++++++++++ setup.py | 8 +- tests/unittests/test_cli.py | 37 +-- 5 files changed, 721 insertions(+), 716 deletions(-) delete mode 100755 bin/cloud-init create mode 100644 cloudinit/cmd/__init__.py create mode 100755 cloudinit/cmd/main.py (limited to 'tests') diff --git a/bin/cloud-init b/bin/cloud-init deleted file mode 100755 index 21c3a684..00000000 --- a/bin/cloud-init +++ /dev/null @@ -1,689 +0,0 @@ -#!/usr/bin/python -# vi: ts=4 expandtab -# -# Copyright (C) 2012 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# Copyright (C) 2012 Yahoo! Inc. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# Author: Joshua Harlow -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import argparse -import json -import os -import sys -import time -import tempfile -import traceback - -# This is more just for running from the bin folder so that -# cloud-init binary can find the cloudinit module -possible_topdir = os.path.normpath(os.path.join(os.path.abspath( - sys.argv[0]), os.pardir, os.pardir)) -if os.path.exists(os.path.join(possible_topdir, "cloudinit", "__init__.py")): - sys.path.insert(0, possible_topdir) - -from cloudinit import patcher -patcher.patch() - -from cloudinit import log as logging -from cloudinit import netinfo -from cloudinit import signal_handler -from cloudinit import sources -from cloudinit import stages -from cloudinit import templater -from cloudinit import util -from cloudinit import reporting -from cloudinit.reporting import events -from cloudinit import version - -from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, PER_ONCE, - CLOUD_CONFIG) - - -# Pretty little cheetah formatted welcome message template -WELCOME_MSG_TPL = ("Cloud-init v. ${version} running '${action}' at " - "${timestamp}. Up ${uptime} seconds.") - -# Module section template -MOD_SECTION_TPL = "cloud_%s_modules" - -# Things u can query on -QUERY_DATA_TYPES = [ - 'data', - 'data_raw', - 'instance_id', -] - -# Frequency shortname to full name -# (so users don't have to remember the full name...) -FREQ_SHORT_NAMES = { - 'instance': PER_INSTANCE, - 'always': PER_ALWAYS, - 'once': PER_ONCE, -} - -LOG = logging.getLogger() - - -# Used for when a logger may not be active -# and we still want to print exceptions... -def print_exc(msg=''): - if msg: - sys.stderr.write("%s\n" % (msg)) - sys.stderr.write('-' * 60) - sys.stderr.write("\n") - traceback.print_exc(file=sys.stderr) - sys.stderr.write('-' * 60) - sys.stderr.write("\n") - - -def welcome(action, msg=None): - if not msg: - msg = welcome_format(action) - util.multi_log("%s\n" % (msg), - console=False, stderr=True, log=LOG) - return msg - - -def welcome_format(action): - tpl_params = { - 'version': version.version_string(), - 'uptime': util.uptime(), - 'timestamp': util.time_rfc2822(), - 'action': action, - } - return templater.render_string(WELCOME_MSG_TPL, tpl_params) - - -def extract_fns(args): - # Files are already opened so lets just pass that along - # since it would of broke if it couldn't have - # read that file already... - fn_cfgs = [] - if args.files: - for fh in args.files: - # The realpath is more useful in logging - # so lets resolve to that... - fn_cfgs.append(os.path.realpath(fh.name)) - return fn_cfgs - - -def run_module_section(mods, action_name, section): - full_section_name = MOD_SECTION_TPL % (section) - (which_ran, failures) = mods.run_section(full_section_name) - total_attempted = len(which_ran) + len(failures) - if total_attempted == 0: - msg = ("No '%s' modules to run" - " under section '%s'") % (action_name, full_section_name) - sys.stderr.write("%s\n" % (msg)) - LOG.debug(msg) - return [] - else: - LOG.debug("Ran %s modules with %s failures", - len(which_ran), len(failures)) - return failures - - -def apply_reporting_cfg(cfg): - if cfg.get('reporting'): - reporting.update_configuration(cfg.get('reporting')) - - -def main_init(name, args): - deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK] - if args.local: - deps = [sources.DEP_FILESYSTEM] - - if not args.local: - # See doc/kernel-cmdline.txt - # - # This is used in maas datasource, in "ephemeral" (read-only root) - # environment where the instance netboots to iscsi ro root. - # and the entity that controls the pxe config has to configure - # the maas datasource. - # - # Could be used elsewhere, only works on network based (not local). - root_name = "%s.d" % (CLOUD_CONFIG) - target_fn = os.path.join(root_name, "91_kernel_cmdline_url.cfg") - util.read_write_cmdline_url(target_fn) - - # Cloud-init 'init' stage is broken up into the following sub-stages - # 1. Ensure that the init object fetches its config without errors - # 2. Setup logging/output redirections with resultant config (if any) - # 3. Initialize the cloud-init filesystem - # 4. Check if we can stop early by looking for various files - # 5. Fetch the datasource - # 6. Connect to the current instance location + update the cache - # 7. Consume the userdata (handlers get activated here) - # 8. Construct the modules object - # 9. Adjust any subsequent logging/output redirections using the modules - # objects config as it may be different from init object - # 10. Run the modules for the 'init' stage - # 11. Done! - if not args.local: - w_msg = welcome_format(name) - else: - w_msg = welcome_format("%s-local" % (name)) - init = stages.Init(ds_deps=deps, reporter=args.reporter) - # Stage 1 - init.read_cfg(extract_fns(args)) - # Stage 2 - outfmt = None - errfmt = None - try: - LOG.debug("Closing stdin") - util.close_stdin() - (outfmt, errfmt) = util.fixup_output(init.cfg, name) - except: - util.logexc(LOG, "Failed to setup output redirection!") - print_exc("Failed to setup output redirection!") - if args.debug: - # Reset so that all the debug handlers are closed out - LOG.debug(("Logging being reset, this logger may no" - " longer be active shortly")) - logging.resetLogging() - logging.setupLogging(init.cfg) - apply_reporting_cfg(init.cfg) - - # Any log usage prior to setupLogging above did not have local user log - # config applied. We send the welcome message now, as stderr/out have - # been redirected and log now configured. - welcome(name, msg=w_msg) - - # Stage 3 - try: - init.initialize() - except Exception: - util.logexc(LOG, "Failed to initialize, likely bad things to come!") - # Stage 4 - path_helper = init.paths - mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK - - if mode == sources.DSMODE_NETWORK: - existing = "trust" - sys.stderr.write("%s\n" % (netinfo.debug_info())) - LOG.debug(("Checking to see if files that we need already" - " exist from a previous run that would allow us" - " to stop early.")) - # no-net is written by upstart cloud-init-nonet when network failed - # to come up - stop_files = [ - os.path.join(path_helper.get_cpath("data"), "no-net"), - ] - existing_files = [] - for fn in stop_files: - if os.path.isfile(fn): - existing_files.append(fn) - - if existing_files: - LOG.debug("[%s] Exiting. stop file %s existed", - mode, existing_files) - return (None, []) - else: - LOG.debug("Execution continuing, no previous run detected that" - " would allow us to stop early.") - else: - existing = "check" - if util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False): - existing = "trust" - - init.purge_cache() - # Delete the non-net file as well - util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net")) - - # Stage 5 - try: - init.fetch(existing=existing) - # if in network mode, and the datasource is local - # then work was done at that stage. - if mode == sources.DSMODE_NETWORK and init.datasource.dsmode != mode: - LOG.debug("[%s] Exiting. datasource %s in local mode", - mode, init.datasource) - return (None, []) - except sources.DataSourceNotFoundException: - # In the case of 'cloud-init init' without '--local' it is a bit - # more likely that the user would consider it failure if nothing was - # found. When using upstart it will also mentions job failure - # in console log if exit code is != 0. - if mode == sources.DSMODE_LOCAL: - LOG.debug("No local datasource found") - else: - util.logexc(LOG, ("No instance datasource found!" - " Likely bad things to come!")) - if not args.force: - init.apply_network_config(bring_up=not args.local) - LOG.debug("[%s] Exiting without datasource in local mode", mode) - if mode == sources.DSMODE_LOCAL: - return (None, []) - else: - return (None, ["No instance datasource found."]) - else: - LOG.debug("[%s] barreling on in force mode without datasource", - mode) - - # Stage 6 - iid = init.instancify() - LOG.debug("[%s] %s will now be targeting instance id: %s. new=%s", - mode, name, iid, init.is_new_instance()) - - init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) - - if mode == sources.DSMODE_LOCAL: - if init.datasource.dsmode != mode: - LOG.debug("[%s] Exiting. datasource %s not in local mode.", - mode, init.datasource) - return (init.datasource, []) - else: - LOG.debug("[%s] %s is in local mode, will apply init modules now.", - mode, init.datasource) - - # update fully realizes user-data (pulling in #include if necessary) - init.update() - # Stage 7 - try: - # Attempt to consume the data per instance. - # This may run user-data handlers and/or perform - # url downloads and such as needed. - (ran, _results) = init.cloudify().run('consume_data', - init.consume_data, - args=[PER_INSTANCE], - freq=PER_INSTANCE) - if not ran: - # Just consume anything that is set to run per-always - # if nothing ran in the per-instance code - # - # See: https://bugs.launchpad.net/bugs/819507 for a little - # reason behind this... - init.consume_data(PER_ALWAYS) - except Exception: - util.logexc(LOG, "Consuming user data failed!") - return (init.datasource, ["Consuming user data failed!"]) - - apply_reporting_cfg(init.cfg) - - # Stage 8 - re-read and apply relevant cloud-config to include user-data - mods = stages.Modules(init, extract_fns(args), reporter=args.reporter) - # Stage 9 - try: - outfmt_orig = outfmt - errfmt_orig = errfmt - (outfmt, errfmt) = util.get_output_cfg(mods.cfg, name) - if outfmt_orig != outfmt or errfmt_orig != errfmt: - LOG.warn("Stdout, stderr changing to (%s, %s)", outfmt, errfmt) - (outfmt, errfmt) = util.fixup_output(mods.cfg, name) - except: - util.logexc(LOG, "Failed to re-adjust output redirection!") - logging.setupLogging(mods.cfg) - - # Stage 10 - return (init.datasource, run_module_section(mods, name, name)) - - -def main_modules(action_name, args): - name = args.mode - # Cloud-init 'modules' stages are broken up into the following sub-stages - # 1. Ensure that the init object fetches its config without errors - # 2. Get the datasource from the init object, if it does - # not exist then that means the main_init stage never - # worked, and thus this stage can not run. - # 3. Construct the modules object - # 4. Adjust any subsequent logging/output redirections using - # the modules objects configuration - # 5. Run the modules for the given stage name - # 6. Done! - w_msg = welcome_format("%s:%s" % (action_name, name)) - init = stages.Init(ds_deps=[], reporter=args.reporter) - # Stage 1 - init.read_cfg(extract_fns(args)) - # Stage 2 - try: - init.fetch(existing="trust") - except sources.DataSourceNotFoundException: - # There was no datasource found, theres nothing to do - msg = ('Can not apply stage %s, no datasource found! Likely bad ' - 'things to come!' % name) - util.logexc(LOG, msg) - print_exc(msg) - if not args.force: - return [(msg)] - # Stage 3 - mods = stages.Modules(init, extract_fns(args), reporter=args.reporter) - # Stage 4 - try: - LOG.debug("Closing stdin") - util.close_stdin() - util.fixup_output(mods.cfg, name) - except: - util.logexc(LOG, "Failed to setup output redirection!") - if args.debug: - # Reset so that all the debug handlers are closed out - LOG.debug(("Logging being reset, this logger may no" - " longer be active shortly")) - logging.resetLogging() - logging.setupLogging(mods.cfg) - apply_reporting_cfg(init.cfg) - - # now that logging is setup and stdout redirected, send welcome - welcome(name, msg=w_msg) - - # Stage 5 - return run_module_section(mods, name, name) - - -def main_query(name, _args): - raise NotImplementedError(("Action '%s' is not" - " currently implemented") % (name)) - - -def main_single(name, args): - # Cloud-init single stage is broken up into the following sub-stages - # 1. Ensure that the init object fetches its config without errors - # 2. Attempt to fetch the datasource (warn if it doesn't work) - # 3. Construct the modules object - # 4. Adjust any subsequent logging/output redirections using - # the modules objects configuration - # 5. Run the single module - # 6. Done! - mod_name = args.name - w_msg = welcome_format(name) - init = stages.Init(ds_deps=[], reporter=args.reporter) - # Stage 1 - init.read_cfg(extract_fns(args)) - # Stage 2 - try: - init.fetch(existing="trust") - except sources.DataSourceNotFoundException: - # There was no datasource found, - # that might be bad (or ok) depending on - # the module being ran (so continue on) - util.logexc(LOG, ("Failed to fetch your datasource," - " likely bad things to come!")) - print_exc(("Failed to fetch your datasource," - " likely bad things to come!")) - if not args.force: - return 1 - # Stage 3 - mods = stages.Modules(init, extract_fns(args), reporter=args.reporter) - mod_args = args.module_args - if mod_args: - LOG.debug("Using passed in arguments %s", mod_args) - mod_freq = args.frequency - if mod_freq: - LOG.debug("Using passed in frequency %s", mod_freq) - mod_freq = FREQ_SHORT_NAMES.get(mod_freq) - # Stage 4 - try: - LOG.debug("Closing stdin") - util.close_stdin() - util.fixup_output(mods.cfg, None) - except: - util.logexc(LOG, "Failed to setup output redirection!") - if args.debug: - # Reset so that all the debug handlers are closed out - LOG.debug(("Logging being reset, this logger may no" - " longer be active shortly")) - logging.resetLogging() - logging.setupLogging(mods.cfg) - apply_reporting_cfg(init.cfg) - - # now that logging is setup and stdout redirected, send welcome - welcome(name, msg=w_msg) - - # Stage 5 - (which_ran, failures) = mods.run_single(mod_name, - mod_args, - mod_freq) - if failures: - LOG.warn("Ran %s but it failed!", mod_name) - return 1 - elif not which_ran: - LOG.warn("Did not run %s, does it exist?", mod_name) - return 1 - else: - # Guess it worked - return 0 - - -def atomic_write_file(path, content, mode='w'): - tf = None - try: - tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(path), - delete=False, mode=mode) - tf.write(content) - tf.close() - os.rename(tf.name, path) - except Exception as e: - if tf is not None: - os.unlink(tf.name) - raise e - - -def atomic_write_json(path, data): - return atomic_write_file(path, json.dumps(data, indent=1) + "\n") - - -def status_wrapper(name, args, data_d=None, link_d=None): - if data_d is None: - data_d = os.path.normpath("/var/lib/cloud/data") - if link_d is None: - link_d = os.path.normpath("/run/cloud-init") - - status_path = os.path.join(data_d, "status.json") - status_link = os.path.join(link_d, "status.json") - result_path = os.path.join(data_d, "result.json") - result_link = os.path.join(link_d, "result.json") - - util.ensure_dirs((data_d, link_d,)) - - (_name, functor) = args.action - - if name == "init": - if args.local: - mode = "init-local" - else: - mode = "init" - elif name == "modules": - mode = "modules-%s" % args.mode - else: - raise ValueError("unknown name: %s" % name) - - modes = ('init', 'init-local', 'modules-config', 'modules-final') - - status = None - if mode == 'init-local': - for f in (status_link, result_link, status_path, result_path): - util.del_file(f) - else: - try: - status = json.loads(util.load_file(status_path)) - except: - pass - - if status is None: - nullstatus = { - 'errors': [], - 'start': None, - 'finished': None, - } - status = {'v1': {}} - for m in modes: - status['v1'][m] = nullstatus.copy() - status['v1']['datasource'] = None - - v1 = status['v1'] - v1['stage'] = mode - v1[mode]['start'] = time.time() - - atomic_write_json(status_path, status) - util.sym_link(os.path.relpath(status_path, link_d), status_link, - force=True) - - try: - ret = functor(name, args) - if mode in ('init', 'init-local'): - (datasource, errors) = ret - if datasource is not None: - v1['datasource'] = str(datasource) - else: - errors = ret - - v1[mode]['errors'] = [str(e) for e in errors] - - except Exception as e: - util.logexc(LOG, "failed stage %s", mode) - print_exc("failed run of stage %s" % mode) - v1[mode]['errors'] = [str(e)] - - v1[mode]['finished'] = time.time() - v1['stage'] = None - - atomic_write_json(status_path, status) - - if mode == "modules-final": - # write the 'finished' file - errors = [] - for m in modes: - if v1[m]['errors']: - errors.extend(v1[m].get('errors', [])) - - atomic_write_json(result_path, - {'v1': {'datasource': v1['datasource'], - 'errors': errors}}) - util.sym_link(os.path.relpath(result_path, link_d), result_link, - force=True) - - return len(v1[mode]['errors']) - - -def main(): - parser = argparse.ArgumentParser() - - # Top level args - parser.add_argument('--version', '-v', action='version', - version='%(prog)s ' + (version.version_string())) - parser.add_argument('--file', '-f', action='append', - dest='files', - help=('additional yaml configuration' - ' files to use'), - type=argparse.FileType('rb')) - parser.add_argument('--debug', '-d', action='store_true', - help=('show additional pre-action' - ' logging (default: %(default)s)'), - default=False) - parser.add_argument('--force', action='store_true', - help=('force running even if no datasource is' - ' found (use at your own risk)'), - dest='force', - default=False) - - parser.set_defaults(reporter=None) - subparsers = parser.add_subparsers() - - # Each action and its sub-options (if any) - parser_init = subparsers.add_parser('init', - help=('initializes cloud-init and' - ' performs initial modules')) - parser_init.add_argument("--local", '-l', action='store_true', - help="start in local mode (default: %(default)s)", - default=False) - # This is used so that we can know which action is selected + - # the functor to use to run this subcommand - parser_init.set_defaults(action=('init', main_init)) - - # These settings are used for the 'config' and 'final' stages - parser_mod = subparsers.add_parser('modules', - help=('activates modules using ' - 'a given configuration key')) - parser_mod.add_argument("--mode", '-m', action='store', - help=("module configuration name " - "to use (default: %(default)s)"), - default='config', - choices=('init', 'config', 'final')) - parser_mod.set_defaults(action=('modules', main_modules)) - - # These settings are used when you want to query information - # stored in the cloud-init data objects/directories/files - parser_query = subparsers.add_parser('query', - help=('query information stored ' - 'in cloud-init')) - parser_query.add_argument("--name", '-n', action="store", - help="item name to query on", - required=True, - choices=QUERY_DATA_TYPES) - parser_query.set_defaults(action=('query', main_query)) - - # This subcommand allows you to run a single module - parser_single = subparsers.add_parser('single', - help=('run a single module ')) - parser_single.set_defaults(action=('single', main_single)) - parser_single.add_argument("--name", '-n', action="store", - help="module name to run", - required=True) - parser_single.add_argument("--frequency", action="store", - help=("frequency of the module"), - required=False, - choices=list(FREQ_SHORT_NAMES.keys())) - parser_single.add_argument("--report", action="store_true", - help="enable reporting", - required=False) - parser_single.add_argument("module_args", nargs="*", - metavar='argument', - help=('any additional arguments to' - ' pass to this module')) - parser_single.set_defaults(action=('single', main_single)) - - args = parser.parse_args() - - # Setup basic logging to start (until reinitialized) - # iff in debug mode... - if args.debug: - logging.setupBasicLogging() - - # Setup signal handlers before running - signal_handler.attach_handlers() - - if not hasattr(args, 'action'): - parser.error('too few arguments') - (name, functor) = args.action - if name in ("modules", "init"): - functor = status_wrapper - - report_on = True - if name == "init": - if args.local: - rname, rdesc = ("init-local", "searching for local datasources") - else: - rname, rdesc = ("init-network", - "searching for network datasources") - elif name == "modules": - rname, rdesc = ("modules-%s" % args.mode, - "running modules for %s" % args.mode) - elif name == "single": - rname, rdesc = ("single/%s" % args.name, - "running single module %s" % args.name) - report_on = args.report - - args.reporter = events.ReportEventStack( - rname, rdesc, reporting_enabled=report_on) - with args.reporter: - return util.log_time( - logfunc=LOG.debug, msg="cloud-init mode '%s'" % name, - get_uptime=True, func=functor, args=(name, args)) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/cloudinit/cmd/__init__.py b/cloudinit/cmd/__init__.py new file mode 100644 index 00000000..da124641 --- /dev/null +++ b/cloudinit/cmd/__init__.py @@ -0,0 +1,21 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2012 Yahoo! Inc. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# Author: Joshua Harlow +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py new file mode 100755 index 00000000..27b7b56d --- /dev/null +++ b/cloudinit/cmd/main.py @@ -0,0 +1,682 @@ +#!/usr/bin/python +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2012 Yahoo! Inc. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# Author: Joshua Harlow +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import json +import os +import sys +import tempfile +import time +import traceback + +from cloudinit import patcher +patcher.patch() + +from cloudinit import log as logging +from cloudinit import netinfo +from cloudinit import signal_handler +from cloudinit import sources +from cloudinit import stages +from cloudinit import templater +from cloudinit import util +from cloudinit import version + +from cloudinit import reporting +from cloudinit.reporting import events + +from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, PER_ONCE, + CLOUD_CONFIG) + + +# Pretty little cheetah formatted welcome message template +WELCOME_MSG_TPL = ("Cloud-init v. ${version} running '${action}' at " + "${timestamp}. Up ${uptime} seconds.") + +# Module section template +MOD_SECTION_TPL = "cloud_%s_modules" + +# Things u can query on +QUERY_DATA_TYPES = [ + 'data', + 'data_raw', + 'instance_id', +] + +# Frequency shortname to full name +# (so users don't have to remember the full name...) +FREQ_SHORT_NAMES = { + 'instance': PER_INSTANCE, + 'always': PER_ALWAYS, + 'once': PER_ONCE, +} + +LOG = logging.getLogger() + + +# Used for when a logger may not be active +# and we still want to print exceptions... +def print_exc(msg=''): + if msg: + sys.stderr.write("%s\n" % (msg)) + sys.stderr.write('-' * 60) + sys.stderr.write("\n") + traceback.print_exc(file=sys.stderr) + sys.stderr.write('-' * 60) + sys.stderr.write("\n") + + +def welcome(action, msg=None): + if not msg: + msg = welcome_format(action) + util.multi_log("%s\n" % (msg), + console=False, stderr=True, log=LOG) + return msg + + +def welcome_format(action): + tpl_params = { + 'version': version.version_string(), + 'uptime': util.uptime(), + 'timestamp': util.time_rfc2822(), + 'action': action, + } + return templater.render_string(WELCOME_MSG_TPL, tpl_params) + + +def extract_fns(args): + # Files are already opened so lets just pass that along + # since it would of broke if it couldn't have + # read that file already... + fn_cfgs = [] + if args.files: + for fh in args.files: + # The realpath is more useful in logging + # so lets resolve to that... + fn_cfgs.append(os.path.realpath(fh.name)) + return fn_cfgs + + +def run_module_section(mods, action_name, section): + full_section_name = MOD_SECTION_TPL % (section) + (which_ran, failures) = mods.run_section(full_section_name) + total_attempted = len(which_ran) + len(failures) + if total_attempted == 0: + msg = ("No '%s' modules to run" + " under section '%s'") % (action_name, full_section_name) + sys.stderr.write("%s\n" % (msg)) + LOG.debug(msg) + return [] + else: + LOG.debug("Ran %s modules with %s failures", + len(which_ran), len(failures)) + return failures + + +def apply_reporting_cfg(cfg): + if cfg.get('reporting'): + reporting.update_configuration(cfg.get('reporting')) + + +def main_init(name, args): + deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK] + if args.local: + deps = [sources.DEP_FILESYSTEM] + + if not args.local: + # See doc/kernel-cmdline.txt + # + # This is used in maas datasource, in "ephemeral" (read-only root) + # environment where the instance netboots to iscsi ro root. + # and the entity that controls the pxe config has to configure + # the maas datasource. + # + # Could be used elsewhere, only works on network based (not local). + root_name = "%s.d" % (CLOUD_CONFIG) + target_fn = os.path.join(root_name, "91_kernel_cmdline_url.cfg") + util.read_write_cmdline_url(target_fn) + + # Cloud-init 'init' stage is broken up into the following sub-stages + # 1. Ensure that the init object fetches its config without errors + # 2. Setup logging/output redirections with resultant config (if any) + # 3. Initialize the cloud-init filesystem + # 4. Check if we can stop early by looking for various files + # 5. Fetch the datasource + # 6. Connect to the current instance location + update the cache + # 7. Consume the userdata (handlers get activated here) + # 8. Construct the modules object + # 9. Adjust any subsequent logging/output redirections using the modules + # objects config as it may be different from init object + # 10. Run the modules for the 'init' stage + # 11. Done! + if not args.local: + w_msg = welcome_format(name) + else: + w_msg = welcome_format("%s-local" % (name)) + init = stages.Init(ds_deps=deps, reporter=args.reporter) + # Stage 1 + init.read_cfg(extract_fns(args)) + # Stage 2 + outfmt = None + errfmt = None + try: + LOG.debug("Closing stdin") + util.close_stdin() + (outfmt, errfmt) = util.fixup_output(init.cfg, name) + except Exception: + util.logexc(LOG, "Failed to setup output redirection!") + print_exc("Failed to setup output redirection!") + if args.debug: + # Reset so that all the debug handlers are closed out + LOG.debug(("Logging being reset, this logger may no" + " longer be active shortly")) + logging.resetLogging() + logging.setupLogging(init.cfg) + apply_reporting_cfg(init.cfg) + + # Any log usage prior to setupLogging above did not have local user log + # config applied. We send the welcome message now, as stderr/out have + # been redirected and log now configured. + welcome(name, msg=w_msg) + + # Stage 3 + try: + init.initialize() + except Exception: + util.logexc(LOG, "Failed to initialize, likely bad things to come!") + # Stage 4 + path_helper = init.paths + mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK + + if mode == sources.DSMODE_NETWORK: + existing = "trust" + sys.stderr.write("%s\n" % (netinfo.debug_info())) + LOG.debug(("Checking to see if files that we need already" + " exist from a previous run that would allow us" + " to stop early.")) + # no-net is written by upstart cloud-init-nonet when network failed + # to come up + stop_files = [ + os.path.join(path_helper.get_cpath("data"), "no-net"), + ] + existing_files = [] + for fn in stop_files: + if os.path.isfile(fn): + existing_files.append(fn) + + if existing_files: + LOG.debug("[%s] Exiting. stop file %s existed", + mode, existing_files) + return (None, []) + else: + LOG.debug("Execution continuing, no previous run detected that" + " would allow us to stop early.") + else: + existing = "check" + if util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False): + existing = "trust" + + init.purge_cache() + # Delete the non-net file as well + util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net")) + + # Stage 5 + try: + init.fetch(existing=existing) + # if in network mode, and the datasource is local + # then work was done at that stage. + if mode == sources.DSMODE_NETWORK and init.datasource.dsmode != mode: + LOG.debug("[%s] Exiting. datasource %s in local mode", + mode, init.datasource) + return (None, []) + except sources.DataSourceNotFoundException: + # In the case of 'cloud-init init' without '--local' it is a bit + # more likely that the user would consider it failure if nothing was + # found. When using upstart it will also mentions job failure + # in console log if exit code is != 0. + if mode == sources.DSMODE_LOCAL: + LOG.debug("No local datasource found") + else: + util.logexc(LOG, ("No instance datasource found!" + " Likely bad things to come!")) + if not args.force: + init.apply_network_config(bring_up=not args.local) + LOG.debug("[%s] Exiting without datasource in local mode", mode) + if mode == sources.DSMODE_LOCAL: + return (None, []) + else: + return (None, ["No instance datasource found."]) + else: + LOG.debug("[%s] barreling on in force mode without datasource", + mode) + + # Stage 6 + iid = init.instancify() + LOG.debug("[%s] %s will now be targeting instance id: %s. new=%s", + mode, name, iid, init.is_new_instance()) + + init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) + + if mode == sources.DSMODE_LOCAL: + if init.datasource.dsmode != mode: + LOG.debug("[%s] Exiting. datasource %s not in local mode.", + mode, init.datasource) + return (init.datasource, []) + else: + LOG.debug("[%s] %s is in local mode, will apply init modules now.", + mode, init.datasource) + + # update fully realizes user-data (pulling in #include if necessary) + init.update() + # Stage 7 + try: + # Attempt to consume the data per instance. + # This may run user-data handlers and/or perform + # url downloads and such as needed. + (ran, _results) = init.cloudify().run('consume_data', + init.consume_data, + args=[PER_INSTANCE], + freq=PER_INSTANCE) + if not ran: + # Just consume anything that is set to run per-always + # if nothing ran in the per-instance code + # + # See: https://bugs.launchpad.net/bugs/819507 for a little + # reason behind this... + init.consume_data(PER_ALWAYS) + except Exception: + util.logexc(LOG, "Consuming user data failed!") + return (init.datasource, ["Consuming user data failed!"]) + + apply_reporting_cfg(init.cfg) + + # Stage 8 - re-read and apply relevant cloud-config to include user-data + mods = stages.Modules(init, extract_fns(args), reporter=args.reporter) + # Stage 9 + try: + outfmt_orig = outfmt + errfmt_orig = errfmt + (outfmt, errfmt) = util.get_output_cfg(mods.cfg, name) + if outfmt_orig != outfmt or errfmt_orig != errfmt: + LOG.warn("Stdout, stderr changing to (%s, %s)", outfmt, errfmt) + (outfmt, errfmt) = util.fixup_output(mods.cfg, name) + except Exception: + util.logexc(LOG, "Failed to re-adjust output redirection!") + logging.setupLogging(mods.cfg) + + # Stage 10 + return (init.datasource, run_module_section(mods, name, name)) + + +def main_modules(action_name, args): + name = args.mode + # Cloud-init 'modules' stages are broken up into the following sub-stages + # 1. Ensure that the init object fetches its config without errors + # 2. Get the datasource from the init object, if it does + # not exist then that means the main_init stage never + # worked, and thus this stage can not run. + # 3. Construct the modules object + # 4. Adjust any subsequent logging/output redirections using + # the modules objects configuration + # 5. Run the modules for the given stage name + # 6. Done! + w_msg = welcome_format("%s:%s" % (action_name, name)) + init = stages.Init(ds_deps=[], reporter=args.reporter) + # Stage 1 + init.read_cfg(extract_fns(args)) + # Stage 2 + try: + init.fetch(existing="trust") + except sources.DataSourceNotFoundException: + # There was no datasource found, theres nothing to do + msg = ('Can not apply stage %s, no datasource found! Likely bad ' + 'things to come!' % name) + util.logexc(LOG, msg) + print_exc(msg) + if not args.force: + return [(msg)] + # Stage 3 + mods = stages.Modules(init, extract_fns(args), reporter=args.reporter) + # Stage 4 + try: + LOG.debug("Closing stdin") + util.close_stdin() + util.fixup_output(mods.cfg, name) + except Exception: + util.logexc(LOG, "Failed to setup output redirection!") + if args.debug: + # Reset so that all the debug handlers are closed out + LOG.debug(("Logging being reset, this logger may no" + " longer be active shortly")) + logging.resetLogging() + logging.setupLogging(mods.cfg) + apply_reporting_cfg(init.cfg) + + # now that logging is setup and stdout redirected, send welcome + welcome(name, msg=w_msg) + + # Stage 5 + return run_module_section(mods, name, name) + + +def main_query(name, _args): + raise NotImplementedError(("Action '%s' is not" + " currently implemented") % (name)) + + +def main_single(name, args): + # Cloud-init single stage is broken up into the following sub-stages + # 1. Ensure that the init object fetches its config without errors + # 2. Attempt to fetch the datasource (warn if it doesn't work) + # 3. Construct the modules object + # 4. Adjust any subsequent logging/output redirections using + # the modules objects configuration + # 5. Run the single module + # 6. Done! + mod_name = args.name + w_msg = welcome_format(name) + init = stages.Init(ds_deps=[], reporter=args.reporter) + # Stage 1 + init.read_cfg(extract_fns(args)) + # Stage 2 + try: + init.fetch(existing="trust") + except sources.DataSourceNotFoundException: + # There was no datasource found, + # that might be bad (or ok) depending on + # the module being ran (so continue on) + util.logexc(LOG, ("Failed to fetch your datasource," + " likely bad things to come!")) + print_exc(("Failed to fetch your datasource," + " likely bad things to come!")) + if not args.force: + return 1 + # Stage 3 + mods = stages.Modules(init, extract_fns(args), reporter=args.reporter) + mod_args = args.module_args + if mod_args: + LOG.debug("Using passed in arguments %s", mod_args) + mod_freq = args.frequency + if mod_freq: + LOG.debug("Using passed in frequency %s", mod_freq) + mod_freq = FREQ_SHORT_NAMES.get(mod_freq) + # Stage 4 + try: + LOG.debug("Closing stdin") + util.close_stdin() + util.fixup_output(mods.cfg, None) + except Exception: + util.logexc(LOG, "Failed to setup output redirection!") + if args.debug: + # Reset so that all the debug handlers are closed out + LOG.debug(("Logging being reset, this logger may no" + " longer be active shortly")) + logging.resetLogging() + logging.setupLogging(mods.cfg) + apply_reporting_cfg(init.cfg) + + # now that logging is setup and stdout redirected, send welcome + welcome(name, msg=w_msg) + + # Stage 5 + (which_ran, failures) = mods.run_single(mod_name, + mod_args, + mod_freq) + if failures: + LOG.warn("Ran %s but it failed!", mod_name) + return 1 + elif not which_ran: + LOG.warn("Did not run %s, does it exist?", mod_name) + return 1 + else: + # Guess it worked + return 0 + + +def atomic_write_file(path, content, mode='w'): + tf = None + try: + tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(path), + delete=False, mode=mode) + tf.write(content) + tf.close() + os.rename(tf.name, path) + except Exception as e: + if tf is not None: + os.unlink(tf.name) + raise e + + +def atomic_write_json(path, data): + return atomic_write_file(path, json.dumps(data, indent=1) + "\n") + + +def status_wrapper(name, args, data_d=None, link_d=None): + if data_d is None: + data_d = os.path.normpath("/var/lib/cloud/data") + if link_d is None: + link_d = os.path.normpath("/run/cloud-init") + + status_path = os.path.join(data_d, "status.json") + status_link = os.path.join(link_d, "status.json") + result_path = os.path.join(data_d, "result.json") + result_link = os.path.join(link_d, "result.json") + + util.ensure_dirs((data_d, link_d,)) + + (_name, functor) = args.action + + if name == "init": + if args.local: + mode = "init-local" + else: + mode = "init" + elif name == "modules": + mode = "modules-%s" % args.mode + else: + raise ValueError("unknown name: %s" % name) + + modes = ('init', 'init-local', 'modules-config', 'modules-final') + + status = None + if mode == 'init-local': + for f in (status_link, result_link, status_path, result_path): + util.del_file(f) + else: + try: + status = json.loads(util.load_file(status_path)) + except Exception: + pass + + if status is None: + nullstatus = { + 'errors': [], + 'start': None, + 'finished': None, + } + status = {'v1': {}} + for m in modes: + status['v1'][m] = nullstatus.copy() + status['v1']['datasource'] = None + + v1 = status['v1'] + v1['stage'] = mode + v1[mode]['start'] = time.time() + + atomic_write_json(status_path, status) + util.sym_link(os.path.relpath(status_path, link_d), status_link, + force=True) + + try: + ret = functor(name, args) + if mode in ('init', 'init-local'): + (datasource, errors) = ret + if datasource is not None: + v1['datasource'] = str(datasource) + else: + errors = ret + + v1[mode]['errors'] = [str(e) for e in errors] + + except Exception as e: + util.logexc(LOG, "failed stage %s", mode) + print_exc("failed run of stage %s" % mode) + v1[mode]['errors'] = [str(e)] + + v1[mode]['finished'] = time.time() + v1['stage'] = None + + atomic_write_json(status_path, status) + + if mode == "modules-final": + # write the 'finished' file + errors = [] + for m in modes: + if v1[m]['errors']: + errors.extend(v1[m].get('errors', [])) + + atomic_write_json(result_path, + {'v1': {'datasource': v1['datasource'], + 'errors': errors}}) + util.sym_link(os.path.relpath(result_path, link_d), result_link, + force=True) + + return len(v1[mode]['errors']) + + +def main(sysv_args=None): + if sysv_args is not None: + parser = argparse.ArgumentParser(prog=sysv_args[0]) + sysv_args = sysv_args[1:] + else: + parser = argparse.ArgumentParser() + + # Top level args + parser.add_argument('--version', '-v', action='version', + version='%(prog)s ' + (version.version_string())) + parser.add_argument('--file', '-f', action='append', + dest='files', + help=('additional yaml configuration' + ' files to use'), + type=argparse.FileType('rb')) + parser.add_argument('--debug', '-d', action='store_true', + help=('show additional pre-action' + ' logging (default: %(default)s)'), + default=False) + parser.add_argument('--force', action='store_true', + help=('force running even if no datasource is' + ' found (use at your own risk)'), + dest='force', + default=False) + + parser.set_defaults(reporter=None) + subparsers = parser.add_subparsers() + + # Each action and its sub-options (if any) + parser_init = subparsers.add_parser('init', + help=('initializes cloud-init and' + ' performs initial modules')) + parser_init.add_argument("--local", '-l', action='store_true', + help="start in local mode (default: %(default)s)", + default=False) + # This is used so that we can know which action is selected + + # the functor to use to run this subcommand + parser_init.set_defaults(action=('init', main_init)) + + # These settings are used for the 'config' and 'final' stages + parser_mod = subparsers.add_parser('modules', + help=('activates modules using ' + 'a given configuration key')) + parser_mod.add_argument("--mode", '-m', action='store', + help=("module configuration name " + "to use (default: %(default)s)"), + default='config', + choices=('init', 'config', 'final')) + parser_mod.set_defaults(action=('modules', main_modules)) + + # These settings are used when you want to query information + # stored in the cloud-init data objects/directories/files + parser_query = subparsers.add_parser('query', + help=('query information stored ' + 'in cloud-init')) + parser_query.add_argument("--name", '-n', action="store", + help="item name to query on", + required=True, + choices=QUERY_DATA_TYPES) + parser_query.set_defaults(action=('query', main_query)) + + # This subcommand allows you to run a single module + parser_single = subparsers.add_parser('single', + help=('run a single module ')) + parser_single.set_defaults(action=('single', main_single)) + parser_single.add_argument("--name", '-n', action="store", + help="module name to run", + required=True) + parser_single.add_argument("--frequency", action="store", + help=("frequency of the module"), + required=False, + choices=list(FREQ_SHORT_NAMES.keys())) + parser_single.add_argument("--report", action="store_true", + help="enable reporting", + required=False) + parser_single.add_argument("module_args", nargs="*", + metavar='argument', + help=('any additional arguments to' + ' pass to this module')) + parser_single.set_defaults(action=('single', main_single)) + + args = parser.parse_args(args=sysv_args) + + # Setup basic logging to start (until reinitialized) + # iff in debug mode... + if args.debug: + logging.setupBasicLogging() + + # Setup signal handlers before running + signal_handler.attach_handlers() + + (name, functor) = args.action + + if name in ("modules", "init"): + functor = status_wrapper + + report_on = True + if name == "init": + if args.local: + rname, rdesc = ("init-local", "searching for local datasources") + else: + rname, rdesc = ("init-network", + "searching for network datasources") + elif name == "modules": + rname, rdesc = ("modules-%s" % args.mode, + "running modules for %s" % args.mode) + elif name == "single": + rname, rdesc = ("single/%s" % args.name, + "running single module %s" % args.name) + report_on = args.report + + args.reporter = events.ReportEventStack( + rname, rdesc, reporting_enabled=report_on) + with args.reporter: + return util.log_time( + logfunc=LOG.debug, msg="cloud-init mode '%s'" % name, + get_uptime=True, func=functor, args=(name, args)) diff --git a/setup.py b/setup.py index 8c8f2402..0af576a9 100755 --- a/setup.py +++ b/setup.py @@ -204,10 +204,14 @@ setuptools.setup( author_email='scott.moser@canonical.com', url='http://launchpad.net/cloud-init/', packages=setuptools.find_packages(exclude=['tests']), - scripts=['bin/cloud-init', - 'tools/cloud-init-per'], + scripts=['tools/cloud-init-per'], license='GPLv3', data_files=data_files, install_requires=requirements, cmdclass=cmdclass, + entry_points={ + 'console_scripts': [ + 'cloud-init = cloudinit.cmd.main:main' + ], + } ) diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py index 163ce64c..8268cd5e 100644 --- a/tests/unittests/test_cli.py +++ b/tests/unittests/test_cli.py @@ -6,7 +6,7 @@ import sys from . import helpers as test_helpers mock = test_helpers.mock -BIN_CLOUDINIT = "bin/cloud-init" +from cloudinit.cmd import main as cli class TestCLI(test_helpers.FilesystemMockingTestCase): @@ -15,35 +15,22 @@ class TestCLI(test_helpers.FilesystemMockingTestCase): super(TestCLI, self).setUp() self.stderr = six.StringIO() self.patchStdoutAndStderr(stderr=self.stderr) - self.sys_exit = mock.MagicMock() - self.patched_funcs.enter_context( - mock.patch.object(sys, 'exit', self.sys_exit)) - - def _call_main(self): - self.patched_funcs.enter_context( - mock.patch.object(sys, 'argv', ['cloud-init'])) - cli = imp.load_module( - 'cli', open(BIN_CLOUDINIT), '', ('', 'r', imp.PY_SOURCE)) + + def _call_main(self, sysv_args=None): + if not sysv_args: + sysv_args = ['cloud-init'] try: - return cli.main() - except Exception: - pass + return cli.main(sysv_args=sysv_args) + except SystemExit as e: + return e.code - @test_helpers.skipIf(not os.path.isfile(BIN_CLOUDINIT), "no bin/cloudinit") def test_no_arguments_shows_usage(self): - self._call_main() - self.assertIn('usage: cloud-init', self.stderr.getvalue()) - - @test_helpers.skipIf(not os.path.isfile(BIN_CLOUDINIT), "no bin/cloudinit") - def test_no_arguments_exits_2(self): exit_code = self._call_main() - if self.sys_exit.call_count: - self.assertEqual(mock.call(2), self.sys_exit.call_args) - else: - self.assertEqual(2, exit_code) + self.assertIn('usage: cloud-init', self.stderr.getvalue()) + self.assertEqual(2, exit_code) - @test_helpers.skipIf(not os.path.isfile(BIN_CLOUDINIT), "no bin/cloudinit") def test_no_arguments_shows_error_message(self): - self._call_main() + exit_code = self._call_main() self.assertIn('cloud-init: error: too few arguments', self.stderr.getvalue()) + self.assertEqual(2, exit_code) -- cgit v1.2.3 From 946b0f5542ffb43149f60b5dd50b2fb450d23713 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 15 Jun 2016 10:31:44 +0100 Subject: Re-apply "Remove trailing dot from GCE metadata URL (LP: #1581200) [Phil Roche]" This commit includes the content of that commit, plus a fix for the tests (provided by Phil). --- ChangeLog | 1 + cloudinit/sources/DataSourceGCE.py | 2 +- tests/unittests/test_datasource/test_gce.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/ChangeLog b/ChangeLog index e2869522..66539792 100644 --- a/ChangeLog +++ b/ChangeLog @@ -122,6 +122,7 @@ - ConfigDrive: improved support for networking information from a network_data.json or older interfaces formated network_config. - Change missing Cheetah log warning to debug [Andrew Jorgensen] + - Remove trailing dot from GCE metadata URL (LP: #1581200) [Phil Roche] 0.7.6: - open 0.7.6 diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py index 9234d1f8..c660a350 100644 --- a/cloudinit/sources/DataSourceGCE.py +++ b/cloudinit/sources/DataSourceGCE.py @@ -25,7 +25,7 @@ from cloudinit import util LOG = logging.getLogger(__name__) BUILTIN_DS_CONFIG = { - 'metadata_url': 'http://metadata.google.internal./computeMetadata/v1/' + 'metadata_url': 'http://metadata.google.internal/computeMetadata/v1/' } REQUIRED_FIELDS = ('instance-id', 'availability-zone', 'local-hostname') diff --git a/tests/unittests/test_datasource/test_gce.py b/tests/unittests/test_datasource/test_gce.py index 1f7eb99e..6e62a4d2 100644 --- a/tests/unittests/test_datasource/test_gce.py +++ b/tests/unittests/test_datasource/test_gce.py @@ -52,7 +52,7 @@ GCE_META_ENCODING = { HEADERS = {'X-Google-Metadata-Request': 'True'} MD_URL_RE = re.compile( - r'http://metadata.google.internal./computeMetadata/v1/.*') + r'http://metadata.google.internal/computeMetadata/v1/.*') def _set_mock_metadata(gce_meta=None): -- cgit v1.2.3 From 417fda64958f04c705860b737787fc921b965ade Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 15 Jun 2016 12:17:05 -0700 Subject: Fix a few tools and tests for newer pep8 --- tests/unittests/test_cli.py | 3 ++- tools/run-pep8 | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py index 8268cd5e..a53e7116 100644 --- a/tests/unittests/test_cli.py +++ b/tests/unittests/test_cli.py @@ -4,10 +4,11 @@ import six import sys from . import helpers as test_helpers -mock = test_helpers.mock from cloudinit.cmd import main as cli +mock = test_helpers.mock + class TestCLI(test_helpers.FilesystemMockingTestCase): diff --git a/tools/run-pep8 b/tools/run-pep8 index 086400fc..4bd0bbfb 100755 --- a/tools/run-pep8 +++ b/tools/run-pep8 @@ -1,8 +1,7 @@ #!/bin/bash -pycheck_dirs=( "cloudinit/" "bin/" "tests/" "tools/" ) -# FIXME: cloud-init modifies sys module path, pep8 does not like -# bin_files=( "bin/cloud-init" ) +pycheck_dirs=( "cloudinit/" "tests/" "tools/" ) + CR=" " [ "$1" = "-v" ] && { verbose="$1"; shift; } || verbose="" -- cgit v1.2.3 From b77f11b35bf0978247fc88d0f4239c5bd4f65d66 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 15 Jun 2016 12:19:12 -0700 Subject: Remove some unused imports --- tests/unittests/test_cli.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'tests') diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py index a53e7116..5fa252f7 100644 --- a/tests/unittests/test_cli.py +++ b/tests/unittests/test_cli.py @@ -1,7 +1,4 @@ -import imp -import os import six -import sys from . import helpers as test_helpers -- cgit v1.2.3 From 459de83024b41c32823b75cf483da994fb1388b7 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 15 Jun 2016 16:11:24 -0700 Subject: Fixup code review comments --- cloudinit/distros/debian.py | 12 ++--- cloudinit/distros/rhel.py | 4 +- cloudinit/net/__init__.py | 1 - cloudinit/net/eni.py | 36 +++++++------ cloudinit/net/renderer.py | 48 +++++++++++++++++ cloudinit/net/sysconfig.py | 123 +++++++++++--------------------------------- tests/unittests/test_net.py | 10 ++-- 7 files changed, 109 insertions(+), 125 deletions(-) create mode 100644 cloudinit/net/renderer.py (limited to 'tests') diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index e71aaa97..511ed4fe 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -57,7 +57,11 @@ class Distro(distros.Distro): # should only happen say once per instance...) self._runner = helpers.Runners(paths) self.osfamily = 'debian' - self._net_renderer = eni.Renderer() + self._net_renderer = eni.Renderer({ + 'eni_path': self.network_conf_fn, + 'links_prefix_path': self.links_prefix, + 'netrules_path': None, + }) def apply_locale(self, locale, out_fn=None): if not out_fn: @@ -82,12 +86,8 @@ class Distro(distros.Distro): def _write_network_config(self, netconfig): ns = net.parse_net_config_data(netconfig) - self._net_renderer.render_network_state( - target="/", network_state=ns, - eni=self.network_conf_fn, links_prefix=self.links_prefix, - netrules=None) + self._net_renderer.render_network_state("/", ns) _maybe_remove_legacy_eth0() - return [] def _bring_up_interfaces(self, device_names): diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index 20525e47..1aa42d75 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -67,8 +67,8 @@ class Distro(distros.Distro): self.package_command('install', pkgs=pkglist) def _write_network_config(self, netconfig): - self._net_renderer.render_network_state( - target="/", parse_net_config_data(netconfig)) + ns = parse_net_config_data(netconfig) + self._net_renderer.render_network_state("/", ns) return [] def _write_network(self, settings): diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index f5668fff..6959ad34 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -26,7 +26,6 @@ from cloudinit import util LOG = logging.getLogger(__name__) SYS_CLASS_NET = "/sys/class/net/" DEFAULT_PRIMARY_INTERFACE = 'eth0' -LINKS_FNAME_PREFIX = "etc/systemd/network/50-cloud-init-" def sys_dev_path(devname, path=""): diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index df0df9b2..352f7dd5 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -16,10 +16,9 @@ import glob import os import re -from . import LINKS_FNAME_PREFIX from . import ParserError -from .udev import generate_udev_rule +from . import renderer from cloudinit import util @@ -297,9 +296,18 @@ def _ifaces_to_net_config_data(ifaces): 'config': [devs[d] for d in sorted(devs)]} -class Renderer(object): +class Renderer(renderer.Renderer): """Renders network information in a /etc/network/interfaces format.""" + def __init__(self, config=None): + if not config: + config = {} + self.eni_path = config.get('eni_path', 'etc/network/interfaces') + self.links_path_prefix = config.get( + 'links_path_prefix', 'etc/systemd/network/50-cloud-init-') + self.netrules_path = config.get( + 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') + def _render_persistent_net(self, network_state): """Given state, emit udev rules to map mac to ifname.""" content = "" @@ -419,29 +427,23 @@ class Renderer(object): content = content.replace('mac_address', 'hwaddress') return content - def render_network_state( - self, target, network_state, eni="etc/network/interfaces", - links_prefix=LINKS_FNAME_PREFIX, - netrules='etc/udev/rules.d/70-persistent-net.rules', - writer=None): - - fpeni = os.path.sep.join((target, eni,)) + def render_network_state(self, target, network_state): + fpeni = os.path.join(target, self.eni_path) util.ensure_dir(os.path.dirname(fpeni)) util.write_file(fpeni, self._render_interfaces(network_state)) - if netrules: - netrules = os.path.sep.join((target, netrules,)) + if self.netrules_path: + netrules = os.path.join(target, self.netrules_path) util.ensure_dir(os.path.dirname(netrules)) util.write_file(netrules, self._render_persistent_net(network_state)) - if links_prefix: + if self.links_path_prefix: self._render_systemd_links(target, network_state, - links_prefix=links_prefix) + links_prefix=self.links_path_prefix) - def _render_systemd_links(self, target, network_state, - links_prefix=LINKS_FNAME_PREFIX): - fp_prefix = os.path.sep.join((target, links_prefix)) + def _render_systemd_links(self, target, network_state, links_prefix): + fp_prefix = os.path.join(target, links_prefix) for f in glob.glob(fp_prefix + "*"): os.unlink(f) for iface in network_state.iter_interfaces(): diff --git a/cloudinit/net/renderer.py b/cloudinit/net/renderer.py new file mode 100644 index 00000000..310cbe0d --- /dev/null +++ b/cloudinit/net/renderer.py @@ -0,0 +1,48 @@ +# Copyright (C) 2013-2014 Canonical Ltd. +# +# Author: Scott Moser +# Author: Blake Rouse +# +# Curtin is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Curtin. If not, see . + +import six + +from .udev import generate_udev_rule + + +def filter_by_type(match_type): + return lambda iface: match_type == iface['type'] + + +def filter_by_name(match_name): + return lambda iface: match_name == iface['name'] + + +filter_by_physical = filter_by_type('physical') + + +class Renderer(object): + + @staticmethod + def _render_persistent_net(network_state): + """Given state, emit udev rules to map mac to ifname.""" + # TODO(harlowja): this seems shared between eni renderer and + # this, so move it to a shared location. + content = six.StringIO() + for iface in network_state.iter_interfaces(filter_by_physical): + # for physical interfaces write out a persist net udev rule + if 'name' in iface and iface.get('mac_address'): + content.write(generate_udev_rule(iface['name'], + iface['mac_address'])) + return content.getvalue() diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 647ca3ed..dd005796 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -20,9 +20,10 @@ import six from cloudinit.distros.parsers import resolv_conf from cloudinit import util -from . import network_state from .udev import generate_udev_rule +from . import renderer + def _make_header(sep='#'): lines = [ @@ -37,17 +38,6 @@ def _make_header(sep='#'): return "\n".join(lines) -def _filter_by_type(match_type): - return lambda iface: match_type == iface['type'] - - -def _filter_by_name(match_name): - return lambda iface: match_name == iface['name'] - - -_filter_by_physical = _filter_by_type('physical') - - def _is_default_route(route): if route['network'] == '::' and route['netmask'] == 0: return True @@ -182,7 +172,7 @@ class NetInterface(ConfigMap): return c -class Renderer(object): +class Renderer(renderer.Renderer): """Renders network information in a /etc/sysconfig format.""" # See: https://access.redhat.com/documentation/en-US/\ @@ -211,18 +201,13 @@ class Renderer(object): ('bridge_bridgeprio', 'PRIO'), ]) - @staticmethod - def _render_persistent_net(network_state): - """Given state, emit udev rules to map mac to ifname.""" - # TODO(harlowja): this seems shared between eni renderer and - # this, so move it to a shared location. - content = six.StringIO() - for iface in network_state.iter_interfaces(_filter_by_physical): - # for physical interfaces write out a persist net udev rule - if 'name' in iface and iface.get('mac_address'): - content.write(generate_udev_rule(iface['name'], - iface['mac_address'])) - return content.getvalue() + def __init__(self, config=None): + if not config: + config = {} + self.sysconf_dir = config.get('sysconf_dir', 'etc/sysconfig/') + self.netrules_path = config.get( + 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') + self.dns_path = config.get('dns_path', 'etc/resolv.conf') @classmethod def _render_iface_shared(cls, iface, iface_cfg): @@ -302,7 +287,7 @@ class Renderer(object): @classmethod def _render_physical_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_physical): + for iface in network_state.iter_interfaces(renderer.filter_by_physical): iface_name = iface['name'] iface_subnets = iface.get("subnets", []) iface_cfg = iface_contents[iface_name] @@ -319,7 +304,7 @@ class Renderer(object): @classmethod def _render_bond_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_type('bond')): + for iface in network_state.iter_interfaces(renderer.filter_by_type('bond')): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] cls._render_bonding_opts(iface_cfg, iface) @@ -337,7 +322,7 @@ class Renderer(object): @staticmethod def _render_vlan_interfaces(network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_type('vlan')): + for iface in network_state.iter_interfaces(renderer.filter_by_type('vlan')): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] iface_cfg['VLAN'] = True @@ -348,15 +333,15 @@ class Renderer(object): content = resolv_conf.ResolvConf("") if existing_dns_path and os.path.isfile(existing_dns_path): content = resolv_conf.ResolvConf(util.load_file(existing_dns_path)) - for ns in network_state.dns_nameservers: - content.add_nameserver(ns) - for d in network_state.dns_searchdomains: - content.add_search_domain(d) + for nameserver in network_state.dns_nameservers: + content.add_nameserver(nameserver) + for searchdomain in network_state.dns_searchdomains: + content.add_search_domain(searchdomain) return "\n".join([_make_header(';'), str(content)]) @classmethod def _render_bridge_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_type('bridge')): + for iface in network_state.iter_interfaces(renderer.filter_by_type('bridge')): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] iface_cfg.kind = 'bridge' @@ -397,67 +382,17 @@ class Renderer(object): contents[iface_cfg.routes.path] = iface_cfg.routes.to_string() return contents - def render_network_state( - self, target, network_state, sysconf_dir="etc/sysconfig/", - netrules='etc/udev/rules.d/70-persistent-net.rules', - dns='etc/resolv.conf'): - if target: - base_sysconf_dir = os.path.join(target, sysconf_dir) - else: - base_sysconf_dir = sysconf_dir + def render_network_state(self, target, network_state): + base_sysconf_dir = os.path.join(target, self.sysconf_dir) for path, data in self._render_sysconfig(base_sysconf_dir, network_state).items(): - if target: - util.write_file(path, data) - else: - print("File to be at: %s" % path) - print(data) - if dns: - if target: - dns_path = os.path.join(target, dns) - resolv_content = self._render_dns(network_state, - existing_dns_path=dns_path) - util.write_file(dns_path, resolv_content) - else: - resolv_content = self._render_dns(network_state) - dns_path = dns - print("File to be at: %s" % dns_path) - print(resolv_content) - if netrules: + util.write_file(path, data) + if self.dns_path: + dns_path = os.path.join(target, self.dns_path) + resolv_content = self._render_dns(network_state, + existing_dns_path=dns_path) + util.write_file(dns_path, resolv_content) + if self.netrules_path: netrules_content = self._render_persistent_net(network_state) - if target: - netrules_path = os.path.join(target, netrules) - util.write_file(netrules_path, netrules_content) - else: - netrules_path = netrules - print("File to be at: %s" % netrules_path) - print(netrules_content) - - -def main(): - """Reads a os network state json file and outputs what would be written.""" - from cloudinit.sources.helpers import openstack - - import argparse - import json - - parser = argparse.ArgumentParser() - parser.add_argument("-f", "--file", metavar="FILE", - help=("openstack network json file" - " to read (required)"), - required=True) - parser.add_argument("-d", "--dir", metavar="DIR", - help=("directory to write output into (if" - " not provided then written to stdout)"), - default=None) - args = parser.parse_args() - - network_json = json.loads(util.load_file(args.file)) - net_state = network_state.parse_net_config_data( - openstack.convert_net_json(network_json), skip_broken=False) - r = Renderer() - r.render_network_state(args.dir, net_state) - - -if __name__ == '__main__': - main() + netrules_path = os.path.join(target, self.netrules_path) + util.write_file(netrules_path, netrules_content) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 43602030..3ae00fc6 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -248,11 +248,11 @@ class TestEniNetRendering(TestCase): render_dir = os.path.join(tmp_dir, "render") os.makedirs(render_dir) - renderer = eni.Renderer() - renderer.render_network_state(render_dir, ns, - eni="interfaces", - links_prefix=None, - netrules=None) + renderer = eni.Renderer( + {'links_path_prefix': None, + 'eni_path': 'interfaces', 'netrules_path': None, + }) + renderer.render_network_state(render_dir, ns) self.assertTrue(os.path.exists(os.path.join(render_dir, 'interfaces'))) -- cgit v1.2.3