diff options
author | Scott Moser <smoser@brickies.net> | 2017-06-29 11:27:18 -0400 |
---|---|---|
committer | Scott Moser <smoser@brickies.net> | 2017-06-29 11:28:56 -0400 |
commit | 9643086947ec55b2750cce64a1f175b73659ca79 (patch) | |
tree | 04055fa93e498762423d5f443505c83d96f09920 /debian | |
parent | 1e730f799afa75c82625c72d7f32f14b05152668 (diff) | |
download | vyos-cloud-init-9643086947ec55b2750cce64a1f175b73659ca79.tar.gz vyos-cloud-init-9643086947ec55b2750cce64a1f175b73659ca79.zip |
drop cherry picks added for sru so trunk can build again.
drop the following cherry picks:
debian/patches/cpick-5fb49bac-azure-identify-platform-by-well-known-value-in-chassis
debian/patches/cpick-003c6678-net-remove-systemd-link-file-writing-from-eni-renderer
debian/patches/cpick-1cd4323b-azure-remove-accidental-duplicate-line-in-merge
debian/patches/cpick-ebc9ecbc-Azure-Add-network-config-Refactor-net-layer-to-handle
debian/patches/cpick-11121fe4-systemd-make-cloud-final.service-run-before-apt-daily
Diffstat (limited to 'debian')
6 files changed, 0 insertions, 1967 deletions
diff --git a/debian/patches/cpick-003c6678-net-remove-systemd-link-file-writing-from-eni-renderer b/debian/patches/cpick-003c6678-net-remove-systemd-link-file-writing-from-eni-renderer deleted file mode 100644 index 76504ccb..00000000 --- a/debian/patches/cpick-003c6678-net-remove-systemd-link-file-writing-from-eni-renderer +++ /dev/null @@ -1,95 +0,0 @@ -From 003c6678e9c873b3b787a814016872b6592f5069 Mon Sep 17 00:00:00 2001 -From: Ryan Harper <ryan.harper@canonical.com> -Date: Thu, 25 May 2017 15:37:15 -0500 -Subject: [PATCH] net: remove systemd link file writing from eni renderer - -During the network v2 merge, we inadvertently re-enabled rendering systemd -.link files. This files are not required as cloud-init already has to do -interface renaming due to issues with udevd which may refuse to rename -certain interfaces (such as veth devices in a LXD container). As such, -removing the code altogether. ---- - cloudinit/net/eni.py | 25 ------------------------- - tests/unittests/test_net.py | 9 +++------ - 2 files changed, 3 insertions(+), 31 deletions(-) - ---- a/cloudinit/net/eni.py -+++ b/cloudinit/net/eni.py -@@ -304,8 +304,6 @@ class Renderer(renderer.Renderer): - config = {} - self.eni_path = config.get('eni_path', 'etc/network/interfaces') - self.eni_header = config.get('eni_header', None) -- 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') - -@@ -451,28 +449,6 @@ class Renderer(renderer.Renderer): - util.write_file(netrules, - self._render_persistent_net(network_state)) - -- if self.links_path_prefix: -- self._render_systemd_links(target, network_state, -- links_prefix=self.links_path_prefix) -- -- def _render_systemd_links(self, target, network_state, links_prefix): -- fp_prefix = util.target_path(target, links_prefix) -- for f in glob.glob(fp_prefix + "*"): -- os.unlink(f) -- for iface in network_state.iter_interfaces(): -- if (iface['type'] == 'physical' and 'name' in iface and -- iface.get('mac_address')): -- fname = fp_prefix + iface['name'] + ".link" -- content = "\n".join([ -- "[Match]", -- "MACAddress=" + iface['mac_address'], -- "", -- "[Link]", -- "Name=" + iface['name'], -- "" -- ]) -- util.write_file(fname, content) -- - - def network_state_to_eni(network_state, header=None, render_hwaddress=False): - # render the provided network state, return a string of equivalent eni -@@ -480,7 +456,6 @@ def network_state_to_eni(network_state, - renderer = Renderer(config={ - 'eni_path': eni_path, - 'eni_header': header, -- 'links_path_prefix': None, - 'netrules_path': None, - }) - if not header: ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -992,9 +992,7 @@ class TestEniNetRendering(CiTestCase): - os.makedirs(render_dir) - - renderer = eni.Renderer( -- {'links_path_prefix': None, -- 'eni_path': 'interfaces', 'netrules_path': None, -- }) -+ {'eni_path': 'interfaces', 'netrules_path': None}) - renderer.render_network_state(ns, render_dir) - - self.assertTrue(os.path.exists(os.path.join(render_dir, -@@ -1376,7 +1374,7 @@ class TestNetplanRoundTrip(CiTestCase): - - class TestEniRoundTrip(CiTestCase): - def _render_and_read(self, network_config=None, state=None, eni_path=None, -- links_prefix=None, netrules_path=None, dir=None): -+ netrules_path=None, dir=None): - if dir is None: - dir = self.tmp_dir() - -@@ -1391,8 +1389,7 @@ class TestEniRoundTrip(CiTestCase): - eni_path = 'etc/network/interfaces' - - renderer = eni.Renderer( -- config={'eni_path': eni_path, 'links_path_prefix': links_prefix, -- 'netrules_path': netrules_path}) -+ config={'eni_path': eni_path, 'netrules_path': netrules_path}) - - renderer.render_network_state(ns, dir) - return dir2dict(dir) diff --git a/debian/patches/cpick-11121fe4-systemd-make-cloud-final.service-run-before-apt-daily b/debian/patches/cpick-11121fe4-systemd-make-cloud-final.service-run-before-apt-daily deleted file mode 100644 index 643a3e2a..00000000 --- a/debian/patches/cpick-11121fe4-systemd-make-cloud-final.service-run-before-apt-daily +++ /dev/null @@ -1,33 +0,0 @@ -From 11121fe4d5af0554140d88685029fa248fa0c7c9 Mon Sep 17 00:00:00 2001 -From: Scott Moser <smoser@brickies.net> -Date: Mon, 12 Jun 2017 14:10:58 -0400 -Subject: [PATCH] systemd: make cloud-final.service run before apt daily - services. - -This changes all cloud-init systemd units to run 'Before' the apt processes -that run daily and may cause a lock on the apt database. - -apt-daily-upgrade.service contains 'After=apt-daily.service'. -Thus following order is enforced, so we can just be 'Before' the first. - apt-daily.service - apt-daily-upgrade.service - -Note that this means only that apt-daily* will not run until -cloud-init has entirely finished. Any other processes running apt-get -operations are still affected by the global lock. - -LP: #1693361 ---- - systemd/cloud-final.service | 1 + - 1 file changed, 1 insertion(+) - ---- a/systemd/cloud-final.service -+++ b/systemd/cloud-final.service -@@ -2,6 +2,7 @@ - Description=Execute cloud user/final scripts - After=network-online.target cloud-config.service rc-local.service multi-user.target - Wants=network-online.target cloud-config.service -+Before=apt-daily.service - - [Service] - Type=oneshot diff --git a/debian/patches/cpick-1cd4323b-azure-remove-accidental-duplicate-line-in-merge b/debian/patches/cpick-1cd4323b-azure-remove-accidental-duplicate-line-in-merge deleted file mode 100644 index 2ddf83ee..00000000 --- a/debian/patches/cpick-1cd4323b-azure-remove-accidental-duplicate-line-in-merge +++ /dev/null @@ -1,22 +0,0 @@ -From 1cd4323b940408aa34dcaa01bd8a7ed43d9a966a Mon Sep 17 00:00:00 2001 -From: Scott Moser <smoser@brickies.net> -Date: Thu, 1 Jun 2017 12:40:12 -0400 -Subject: [PATCH] azure: remove accidental duplicate line in merge. - -In previous commit I inadvertantly left two calls to - asset_tag = util.read_dmi_data('chassis-asset-tag') -The second did not do anything useful. Thus, remove it. ---- - cloudinit/sources/DataSourceAzure.py | 1 - - 1 file changed, 1 deletion(-) - ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -326,7 +326,6 @@ class DataSourceAzureNet(sources.DataSou - if asset_tag != AZURE_CHASSIS_ASSET_TAG: - LOG.debug("Non-Azure DMI asset tag '%s' discovered.", asset_tag) - return False -- asset_tag = util.read_dmi_data('chassis-asset-tag') - ddir = self.ds_cfg['data_dir'] - - candidates = [self.seed_dir] diff --git a/debian/patches/cpick-5fb49bac-azure-identify-platform-by-well-known-value-in-chassis b/debian/patches/cpick-5fb49bac-azure-identify-platform-by-well-known-value-in-chassis deleted file mode 100644 index 4bcda2d0..00000000 --- a/debian/patches/cpick-5fb49bac-azure-identify-platform-by-well-known-value-in-chassis +++ /dev/null @@ -1,338 +0,0 @@ -From 5fb49bacf7441d8d20a7b4e0e7008ca586f5ebab Mon Sep 17 00:00:00 2001 -From: Chad Smith <chad.smith@canonical.com> -Date: Tue, 30 May 2017 10:28:05 -0600 -Subject: [PATCH] azure: identify platform by well known value in chassis asset - tag. - -Azure sets a known chassis asset tag to 7783-7084-3265-9085-8269-3286-77. -We can inspect this in both ds-identify and DataSource.get_data to -determine whether we are on Azure. - -Added unit tests to cover these changes -and some minor tweaks to Exception error message content to give more -context on malformed or missing ovf-env.xml files. - -LP: #1693939 ---- - cloudinit/sources/DataSourceAzure.py | 9 +++- - tests/unittests/test_datasource/test_azure.py | 66 +++++++++++++++++++++++++-- - tests/unittests/test_ds_identify.py | 39 ++++++++++++++++ - tools/ds-identify | 35 +++++++++----- - 4 files changed, 134 insertions(+), 15 deletions(-) - ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -36,6 +36,8 @@ RESOURCE_DISK_PATH = '/dev/disk/cloud/az - DEFAULT_PRIMARY_NIC = 'eth0' - LEASE_FILE = '/var/lib/dhcp/dhclient.eth0.leases' - DEFAULT_FS = 'ext4' -+# DMI chassis-asset-tag is set static for all azure instances -+AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77' - - - def find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid): -@@ -320,6 +322,11 @@ class DataSourceAzureNet(sources.DataSou - # azure removes/ejects the cdrom containing the ovf-env.xml - # file on reboot. So, in order to successfully reboot we - # need to look in the datadir and consider that valid -+ asset_tag = util.read_dmi_data('chassis-asset-tag') -+ if asset_tag != AZURE_CHASSIS_ASSET_TAG: -+ LOG.debug("Non-Azure DMI asset tag '%s' discovered.", asset_tag) -+ return False -+ asset_tag = util.read_dmi_data('chassis-asset-tag') - ddir = self.ds_cfg['data_dir'] - - candidates = [self.seed_dir] -@@ -694,7 +701,7 @@ def read_azure_ovf(contents): - try: - dom = minidom.parseString(contents) - except Exception as e: -- raise BrokenAzureDataSource("invalid xml: %s" % e) -+ raise BrokenAzureDataSource("Invalid ovf-env.xml: %s" % e) - - results = find_child(dom.documentElement, - lambda n: n.localName == "ProvisioningSection") ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -76,7 +76,9 @@ def construct_valid_ovf_env(data=None, p - return content - - --class TestAzureDataSource(TestCase): -+class TestAzureDataSource(CiTestCase): -+ -+ with_logs = True - - def setUp(self): - super(TestAzureDataSource, self).setUp() -@@ -160,6 +162,12 @@ scbus-1 on xpt0 bus 0 - - self.instance_id = 'test-instance-id' - -+ def _dmi_mocks(key): -+ if key == 'system-uuid': -+ return self.instance_id -+ elif key == 'chassis-asset-tag': -+ return '7783-7084-3265-9085-8269-3286-77' -+ - self.apply_patches([ - (dsaz, 'list_possible_azure_ds_devs', dsdevs), - (dsaz, 'invoke_agent', _invoke_agent), -@@ -170,7 +178,7 @@ scbus-1 on xpt0 bus 0 - (dsaz, 'set_hostname', mock.MagicMock()), - (dsaz, 'get_metadata_from_fabric', self.get_metadata_from_fabric), - (dsaz.util, 'read_dmi_data', mock.MagicMock( -- return_value=self.instance_id)), -+ side_effect=_dmi_mocks)), - ]) - - dsrc = dsaz.DataSourceAzureNet( -@@ -241,6 +249,23 @@ fdescfs /dev/fd fdes - res = get_path_dev_freebsd('/etc', mnt_list) - self.assertIsNotNone(res) - -+ @mock.patch('cloudinit.sources.DataSourceAzure.util.read_dmi_data') -+ def test_non_azure_dmi_chassis_asset_tag(self, m_read_dmi_data): -+ """Report non-azure when DMI's chassis asset tag doesn't match. -+ -+ Return False when the asset tag doesn't match Azure's static -+ AZURE_CHASSIS_ASSET_TAG. -+ """ -+ # Return a non-matching asset tag value -+ nonazure_tag = dsaz.AZURE_CHASSIS_ASSET_TAG + 'X' -+ m_read_dmi_data.return_value = nonazure_tag -+ dsrc = dsaz.DataSourceAzureNet( -+ {}, distro=None, paths=self.paths) -+ self.assertFalse(dsrc.get_data()) -+ self.assertEqual( -+ "Non-Azure DMI asset tag '{0}' discovered.\n".format(nonazure_tag), -+ self.logs.getvalue()) -+ - def test_basic_seed_dir(self): - odata = {'HostName': "myhost", 'UserName': "myuser"} - data = {'ovfcontent': construct_valid_ovf_env(data=odata), -@@ -531,9 +556,17 @@ class TestAzureBounce(TestCase): - self.patches.enter_context( - mock.patch.object(dsaz, 'get_metadata_from_fabric', - mock.MagicMock(return_value={}))) -+ -+ def _dmi_mocks(key): -+ if key == 'system-uuid': -+ return 'test-instance-id' -+ elif key == 'chassis-asset-tag': -+ return '7783-7084-3265-9085-8269-3286-77' -+ raise RuntimeError('should not get here') -+ - self.patches.enter_context( - mock.patch.object(dsaz.util, 'read_dmi_data', -- mock.MagicMock(return_value='test-instance-id'))) -+ mock.MagicMock(side_effect=_dmi_mocks))) - - def setUp(self): - super(TestAzureBounce, self).setUp() -@@ -696,6 +729,33 @@ class TestAzureBounce(TestCase): - self.assertEqual(0, self.set_hostname.call_count) - - -+class TestLoadAzureDsDir(CiTestCase): -+ """Tests for load_azure_ds_dir.""" -+ -+ def setUp(self): -+ self.source_dir = self.tmp_dir() -+ super(TestLoadAzureDsDir, self).setUp() -+ -+ def test_missing_ovf_env_xml_raises_non_azure_datasource_error(self): -+ """load_azure_ds_dir raises an error When ovf-env.xml doesn't exit.""" -+ with self.assertRaises(dsaz.NonAzureDataSource) as context_manager: -+ dsaz.load_azure_ds_dir(self.source_dir) -+ self.assertEqual( -+ 'No ovf-env file found', -+ str(context_manager.exception)) -+ -+ def test_wb_invalid_ovf_env_xml_calls_read_azure_ovf(self): -+ """load_azure_ds_dir calls read_azure_ovf to parse the xml.""" -+ ovf_path = os.path.join(self.source_dir, 'ovf-env.xml') -+ with open(ovf_path, 'wb') as stream: -+ stream.write(b'invalid xml') -+ with self.assertRaises(dsaz.BrokenAzureDataSource) as context_manager: -+ dsaz.load_azure_ds_dir(self.source_dir) -+ self.assertEqual( -+ 'Invalid ovf-env.xml: syntax error: line 1, column 0', -+ str(context_manager.exception)) -+ -+ - class TestReadAzureOvf(TestCase): - def test_invalid_xml_raises_non_azure_ds(self): - invalid_xml = "<foo>" + construct_valid_ovf_env(data={}) ---- a/tests/unittests/test_ds_identify.py -+++ b/tests/unittests/test_ds_identify.py -@@ -39,9 +39,11 @@ RC_FOUND = 0 - RC_NOT_FOUND = 1 - DS_NONE = 'None' - -+P_CHASSIS_ASSET_TAG = "sys/class/dmi/id/chassis_asset_tag" - P_PRODUCT_NAME = "sys/class/dmi/id/product_name" - P_PRODUCT_SERIAL = "sys/class/dmi/id/product_serial" - P_PRODUCT_UUID = "sys/class/dmi/id/product_uuid" -+P_SEED_DIR = "var/lib/cloud/seed" - P_DSID_CFG = "etc/cloud/ds-identify.cfg" - - MOCK_VIRT_IS_KVM = {'name': 'detect_virt', 'RET': 'kvm', 'ret': 0} -@@ -160,6 +162,30 @@ class TestDsIdentify(CiTestCase): - _print_run_output(rc, out, err, cfg, files) - return rc, out, err, cfg, files - -+ def test_wb_print_variables(self): -+ """_print_info reports an array of discovered variables to stderr.""" -+ data = VALID_CFG['Azure-dmi-detection'] -+ _, _, err, _, _ = self._call_via_dict(data) -+ expected_vars = [ -+ 'DMI_PRODUCT_NAME', 'DMI_SYS_VENDOR', 'DMI_PRODUCT_SERIAL', -+ 'DMI_PRODUCT_UUID', 'PID_1_PRODUCT_NAME', 'DMI_CHASSIS_ASSET_TAG', -+ 'FS_LABELS', 'KERNEL_CMDLINE', 'VIRT', 'UNAME_KERNEL_NAME', -+ 'UNAME_KERNEL_RELEASE', 'UNAME_KERNEL_VERSION', 'UNAME_MACHINE', -+ 'UNAME_NODENAME', 'UNAME_OPERATING_SYSTEM', 'DSNAME', 'DSLIST', -+ 'MODE', 'ON_FOUND', 'ON_MAYBE', 'ON_NOTFOUND'] -+ for var in expected_vars: -+ self.assertIn('{0}='.format(var), err) -+ -+ def test_azure_dmi_detection_from_chassis_asset_tag(self): -+ """Azure datasource is detected from DMI chassis-asset-tag""" -+ self._test_ds_found('Azure-dmi-detection') -+ -+ def test_azure_seed_file_detection(self): -+ """Azure datasource is detected due to presence of a seed file. -+ -+ The seed file tested is /var/lib/cloud/seed/azure/ovf-env.xml.""" -+ self._test_ds_found('Azure-seed-detection') -+ - def test_aws_ec2_hvm(self): - """EC2: hvm instances use dmi serial and uuid starting with 'ec2'.""" - self._test_ds_found('Ec2-hvm') -@@ -254,6 +280,19 @@ def _print_run_output(rc, out, err, cfg, - - - VALID_CFG = { -+ 'Azure-dmi-detection': { -+ 'ds': 'Azure', -+ 'files': { -+ P_CHASSIS_ASSET_TAG: '7783-7084-3265-9085-8269-3286-77\n', -+ } -+ }, -+ 'Azure-seed-detection': { -+ 'ds': 'Azure', -+ 'files': { -+ P_CHASSIS_ASSET_TAG: 'No-match\n', -+ os.path.join(P_SEED_DIR, 'azure', 'ovf-env.xml'): 'present\n', -+ } -+ }, - 'Ec2-hvm': { - 'ds': 'Ec2', - 'mocks': [{'name': 'detect_virt', 'RET': 'kvm', 'ret': 0}], ---- a/tools/ds-identify -+++ b/tools/ds-identify -@@ -85,6 +85,7 @@ DI_MAIN=${DI_MAIN:-main} - - DI_DEFAULT_POLICY="search,found=all,maybe=all,notfound=${DI_DISABLED}" - DI_DEFAULT_POLICY_NO_DMI="search,found=all,maybe=all,notfound=${DI_ENABLED}" -+DI_DMI_CHASSIS_ASSET_TAG="" - DI_DMI_PRODUCT_NAME="" - DI_DMI_SYS_VENDOR="" - DI_DMI_PRODUCT_SERIAL="" -@@ -258,6 +259,12 @@ read_kernel_cmdline() { - DI_KERNEL_CMDLINE="$cmdline" - } - -+read_dmi_chassis_asset_tag() { -+ cached "${DI_DMI_CHASSIS_ASSET_TAG}" && return -+ get_dmi_field chassis_asset_tag -+ DI_DMI_CHASSIS_ASSET_TAG="$_RET" -+} -+ - read_dmi_sys_vendor() { - cached "${DI_DMI_SYS_VENDOR}" && return - get_dmi_field sys_vendor -@@ -385,6 +392,14 @@ read_pid1_product_name() { - DI_PID_1_PRODUCT_NAME="$product_name" - } - -+dmi_chassis_asset_tag_matches() { -+ is_container && return 1 -+ case "${DI_DMI_CHASSIS_ASSET_TAG}" in -+ $1) return 0;; -+ esac -+ return 1 -+} -+ - dmi_product_name_matches() { - is_container && return 1 - case "${DI_DMI_PRODUCT_NAME}" in -@@ -401,11 +416,6 @@ dmi_product_serial_matches() { - return 1 - } - --dmi_product_name_is() { -- is_container && return 1 -- [ "${DI_DMI_PRODUCT_NAME}" = "$1" ] --} -- - dmi_sys_vendor_is() { - is_container && return 1 - [ "${DI_DMI_SYS_VENDOR}" = "$1" ] -@@ -477,7 +487,7 @@ dscheck_CloudStack() { - - dscheck_CloudSigma() { - # http://paste.ubuntu.com/23624795/ -- dmi_product_name_is "CloudSigma" && return $DS_FOUND -+ dmi_product_name_matches "CloudSigma" && return $DS_FOUND - return $DS_NOT_FOUND - } - -@@ -653,6 +663,8 @@ dscheck_Azure() { - # UUID="112D211272645f72" LABEL="rd_rdfe_stable.161212-1209" - # TYPE="udf">/dev/sr0</device> - # -+ local azure_chassis="7783-7084-3265-9085-8269-3286-77" -+ dmi_chassis_asset_tag_matches "${azure_chassis}" && return $DS_FOUND - check_seed_dir azure ovf-env.xml && return ${DS_FOUND} - - [ "${DI_VIRT}" = "microsoft" ] || return ${DS_NOT_FOUND} -@@ -785,7 +797,7 @@ dscheck_Ec2() { - } - - dscheck_GCE() { -- if dmi_product_name_is "Google Compute Engine"; then -+ if dmi_product_name_matches "Google Compute Engine"; then - return ${DS_FOUND} - fi - # product name is not guaranteed (LP: #1674861) -@@ -806,10 +818,10 @@ dscheck_OpenStack() { - return ${DS_NOT_FOUND} - fi - local nova="OpenStack Nova" compute="OpenStack Compute" -- if dmi_product_name_is "$nova"; then -+ if dmi_product_name_matches "$nova"; then - return ${DS_FOUND} - fi -- if dmi_product_name_is "$compute"; then -+ if dmi_product_name_matches "$compute"; then - # RDO installed nova (LP: #1675349). - return ${DS_FOUND} - fi -@@ -887,6 +899,7 @@ collect_info() { - read_config - read_datasource_list - read_dmi_sys_vendor -+ read_dmi_chassis_asset_tag - read_dmi_product_name - read_dmi_product_serial - read_dmi_product_uuid -@@ -901,7 +914,7 @@ print_info() { - _print_info() { - local n="" v="" vars="" - vars="DMI_PRODUCT_NAME DMI_SYS_VENDOR DMI_PRODUCT_SERIAL" -- vars="$vars DMI_PRODUCT_UUID PID_1_PRODUCT_NAME" -+ vars="$vars DMI_PRODUCT_UUID PID_1_PRODUCT_NAME DMI_CHASSIS_ASSET_TAG" - vars="$vars FS_LABELS KERNEL_CMDLINE VIRT" - vars="$vars UNAME_KERNEL_NAME UNAME_KERNEL_RELEASE UNAME_KERNEL_VERSION" - vars="$vars UNAME_MACHINE UNAME_NODENAME UNAME_OPERATING_SYSTEM" diff --git a/debian/patches/cpick-ebc9ecbc-Azure-Add-network-config-Refactor-net-layer-to-handle b/debian/patches/cpick-ebc9ecbc-Azure-Add-network-config-Refactor-net-layer-to-handle deleted file mode 100644 index 814f2ef1..00000000 --- a/debian/patches/cpick-ebc9ecbc-Azure-Add-network-config-Refactor-net-layer-to-handle +++ /dev/null @@ -1,1474 +0,0 @@ -From ebc9ecbc8a76bdf511a456fb72339a7eb4c20568 Mon Sep 17 00:00:00 2001 -From: Ryan Harper <ryan.harper@canonical.com> -Date: Tue, 20 Jun 2017 17:06:43 -0500 -Subject: [PATCH] Azure: Add network-config, Refactor net layer to handle - duplicate macs. - -On systems with network devices with duplicate mac addresses, cloud-init -will fail to rename the devices according to the specified network -configuration. Refactor net layer to search by device driver and device -id if available. Azure systems may have duplicate mac addresses by -design. - -Update Azure datasource to run at init-local time and let Azure datasource -generate a fallback networking config to handle advanced networking -configurations. - -Lastly, add a 'setup' method to the datasources that is called before -userdata/vendordata is processed but after networking is up. That is -used here on Azure to interact with the 'fabric'. ---- - cloudinit/cmd/main.py | 3 + - cloudinit/net/__init__.py | 181 ++++++++-- - cloudinit/net/eni.py | 2 + - cloudinit/net/renderer.py | 4 +- - cloudinit/net/udev.py | 7 +- - cloudinit/sources/DataSourceAzure.py | 114 +++++- - cloudinit/sources/__init__.py | 15 +- - cloudinit/stages.py | 5 + - tests/unittests/test_datasource/test_azure.py | 174 +++++++-- - tests/unittests/test_datasource/test_common.py | 2 +- - tests/unittests/test_net.py | 478 ++++++++++++++++++++++++- - 11 files changed, 887 insertions(+), 98 deletions(-) - ---- a/cloudinit/cmd/main.py -+++ b/cloudinit/cmd/main.py -@@ -373,6 +373,9 @@ def main_init(name, args): - LOG.debug("[%s] %s is in local mode, will apply init modules now.", - mode, init.datasource) - -+ # Give the datasource a chance to use network resources. -+ # This is used on Azure to communicate with the fabric over network. -+ init.setup_datasource() - # update fully realizes user-data (pulling in #include if necessary) - init.update() - # Stage 7 ---- a/cloudinit/net/__init__.py -+++ b/cloudinit/net/__init__.py -@@ -86,6 +86,10 @@ def is_bridge(devname): - return os.path.exists(sys_dev_path(devname, "bridge")) - - -+def is_bond(devname): -+ return os.path.exists(sys_dev_path(devname, "bonding")) -+ -+ - def is_vlan(devname): - uevent = str(read_sys_net_safe(devname, "uevent")) - return 'DEVTYPE=vlan' in uevent.splitlines() -@@ -113,6 +117,26 @@ def is_present(devname): - return os.path.exists(sys_dev_path(devname)) - - -+def device_driver(devname): -+ """Return the device driver for net device named 'devname'.""" -+ driver = None -+ driver_path = sys_dev_path(devname, "device/driver") -+ # driver is a symlink to the driver *dir* -+ if os.path.islink(driver_path): -+ driver = os.path.basename(os.readlink(driver_path)) -+ -+ return driver -+ -+ -+def device_devid(devname): -+ """Return the device id string for net device named 'devname'.""" -+ dev_id = read_sys_net_safe(devname, "device/device") -+ if dev_id is False: -+ return None -+ -+ return dev_id -+ -+ - def get_devicelist(): - return os.listdir(SYS_CLASS_NET) - -@@ -127,12 +151,21 @@ def is_disabled_cfg(cfg): - return cfg.get('config') == "disabled" - - --def generate_fallback_config(): -+def generate_fallback_config(blacklist_drivers=None, config_driver=None): - """Determine which attached net dev is most likely to have a connection and - generate network state to run dhcp on that interface""" -+ -+ if not config_driver: -+ config_driver = False -+ -+ if not blacklist_drivers: -+ blacklist_drivers = [] -+ - # get list of interfaces that could have connections - invalid_interfaces = set(['lo']) -- potential_interfaces = set(get_devicelist()) -+ potential_interfaces = set([device for device in get_devicelist() -+ if device_driver(device) not in -+ blacklist_drivers]) - potential_interfaces = potential_interfaces.difference(invalid_interfaces) - # sort into interfaces with carrier, interfaces which could have carrier, - # and ignore interfaces that are definitely disconnected -@@ -144,6 +177,9 @@ def generate_fallback_config(): - if is_bridge(interface): - # skip any bridges - continue -+ if is_bond(interface): -+ # skip any bonds -+ continue - carrier = read_sys_net_int(interface, 'carrier') - if carrier: - connected.append(interface) -@@ -183,9 +219,18 @@ def generate_fallback_config(): - break - if target_mac and target_name: - nconf = {'config': [], 'version': 1} -- nconf['config'].append( -- {'type': 'physical', 'name': target_name, -- 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]}) -+ cfg = {'type': 'physical', 'name': target_name, -+ 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]} -+ # inject the device driver name, dev_id into config if enabled and -+ # device has a valid device driver value -+ if config_driver: -+ driver = device_driver(target_name) -+ if driver: -+ cfg['params'] = { -+ 'driver': driver, -+ 'device_id': device_devid(target_name), -+ } -+ nconf['config'].append(cfg) - return nconf - else: - # can't read any interfaces addresses (or there are none); give up -@@ -206,10 +251,16 @@ def apply_network_config_names(netcfg, s - if ent.get('type') != 'physical': - continue - mac = ent.get('mac_address') -- name = ent.get('name') - if not mac: - continue -- renames.append([mac, name]) -+ name = ent.get('name') -+ driver = ent.get('params', {}).get('driver') -+ device_id = ent.get('params', {}).get('device_id') -+ if not driver: -+ driver = device_driver(name) -+ if not device_id: -+ device_id = device_devid(name) -+ renames.append([mac, name, driver, device_id]) - - return _rename_interfaces(renames) - -@@ -234,15 +285,27 @@ def _get_current_rename_info(check_downa - """Collect information necessary for rename_interfaces. - - returns a dictionary by mac address like: -- {mac: -- {'name': name -- 'up': boolean: is_up(name), -+ {name: -+ { - 'downable': None or boolean indicating that the -- device has only automatically assigned ip addrs.}} -+ device has only automatically assigned ip addrs. -+ 'device_id': Device id value (if it has one) -+ 'driver': Device driver (if it has one) -+ 'mac': mac address -+ 'name': name -+ 'up': boolean: is_up(name) -+ }} - """ -- bymac = {} -- for mac, name in get_interfaces_by_mac().items(): -- bymac[mac] = {'name': name, 'up': is_up(name), 'downable': None} -+ cur_info = {} -+ for (name, mac, driver, device_id) in get_interfaces(): -+ cur_info[name] = { -+ 'downable': None, -+ 'device_id': device_id, -+ 'driver': driver, -+ 'mac': mac, -+ 'name': name, -+ 'up': is_up(name), -+ } - - if check_downable: - nmatch = re.compile(r"[0-9]+:\s+(\w+)[@:]") -@@ -254,11 +317,11 @@ def _get_current_rename_info(check_downa - for bytes_out in (ipv6, ipv4): - nics_with_addresses.update(nmatch.findall(bytes_out)) - -- for d in bymac.values(): -+ for d in cur_info.values(): - d['downable'] = (d['up'] is False or - d['name'] not in nics_with_addresses) - -- return bymac -+ return cur_info - - - def _rename_interfaces(renames, strict_present=True, strict_busy=True, -@@ -271,15 +334,15 @@ def _rename_interfaces(renames, strict_p - if current_info is None: - current_info = _get_current_rename_info() - -- cur_bymac = {} -- for mac, data in current_info.items(): -+ cur_info = {} -+ for name, data in current_info.items(): - cur = data.copy() -- cur['mac'] = mac -- cur_bymac[mac] = cur -+ cur['name'] = name -+ cur_info[name] = cur - - def update_byname(bymac): - return dict((data['name'], data) -- for data in bymac.values()) -+ for data in cur_info.values()) - - def rename(cur, new): - util.subp(["ip", "link", "set", cur, "name", new], capture=True) -@@ -293,14 +356,48 @@ def _rename_interfaces(renames, strict_p - ops = [] - errors = [] - ups = [] -- cur_byname = update_byname(cur_bymac) -+ cur_byname = update_byname(cur_info) - tmpname_fmt = "cirename%d" - tmpi = -1 - -- for mac, new_name in renames: -- cur = cur_bymac.get(mac, {}) -- cur_name = cur.get('name') -+ def entry_match(data, mac, driver, device_id): -+ """match if set and in data""" -+ if mac and driver and device_id: -+ return (data['mac'] == mac and -+ data['driver'] == driver and -+ data['device_id'] == device_id) -+ elif mac and driver: -+ return (data['mac'] == mac and -+ data['driver'] == driver) -+ elif mac: -+ return (data['mac'] == mac) -+ -+ return False -+ -+ def find_entry(mac, driver, device_id): -+ match = [data for data in cur_info.values() -+ if entry_match(data, mac, driver, device_id)] -+ if len(match): -+ if len(match) > 1: -+ msg = ('Failed to match a single device. Matched devices "%s"' -+ ' with search values "(mac:%s driver:%s device_id:%s)"' -+ % (match, mac, driver, device_id)) -+ raise ValueError(msg) -+ return match[0] -+ -+ return None -+ -+ for mac, new_name, driver, device_id in renames: - cur_ops = [] -+ cur = find_entry(mac, driver, device_id) -+ if not cur: -+ if strict_present: -+ errors.append( -+ "[nic not present] Cannot rename mac=%s to %s" -+ ", not available." % (mac, new_name)) -+ continue -+ -+ cur_name = cur.get('name') - if cur_name == new_name: - # nothing to do - continue -@@ -340,13 +437,13 @@ def _rename_interfaces(renames, strict_p - - cur_ops.append(("rename", mac, new_name, (new_name, tmp_name))) - target['name'] = tmp_name -- cur_byname = update_byname(cur_bymac) -+ cur_byname = update_byname(cur_info) - if target['up']: - ups.append(("up", mac, new_name, (tmp_name,))) - - cur_ops.append(("rename", mac, new_name, (cur['name'], new_name))) - cur['name'] = new_name -- cur_byname = update_byname(cur_bymac) -+ cur_byname = update_byname(cur_info) - ops += cur_ops - - opmap = {'rename': rename, 'down': down, 'up': up} -@@ -415,6 +512,36 @@ def get_interfaces_by_mac(): - return ret - - -+def get_interfaces(): -+ """Return list of interface tuples (name, mac, driver, device_id) -+ -+ Bridges and any devices that have a 'stolen' mac are excluded.""" -+ try: -+ devs = get_devicelist() -+ except OSError as e: -+ if e.errno == errno.ENOENT: -+ devs = [] -+ else: -+ raise -+ ret = [] -+ empty_mac = '00:00:00:00:00:00' -+ for name in devs: -+ if not interface_has_own_mac(name): -+ continue -+ if is_bridge(name): -+ continue -+ if is_vlan(name): -+ continue -+ mac = get_interface_mac(name) -+ # some devices may not have a mac (tun0) -+ if not mac: -+ continue -+ if mac == empty_mac and name != 'lo': -+ continue -+ ret.append((name, mac, device_driver(name), device_devid(name))) -+ return ret -+ -+ - class RendererNotFoundError(RuntimeError): - pass - ---- a/cloudinit/net/eni.py -+++ b/cloudinit/net/eni.py -@@ -68,6 +68,8 @@ def _iface_add_attrs(iface, index): - content = [] - ignore_map = [ - 'control', -+ 'device_id', -+ 'driver', - 'index', - 'inet', - 'mode', ---- a/cloudinit/net/renderer.py -+++ b/cloudinit/net/renderer.py -@@ -34,8 +34,10 @@ class Renderer(object): - 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'): -+ driver = iface.get('driver', None) - content.write(generate_udev_rule(iface['name'], -- iface['mac_address'])) -+ iface['mac_address'], -+ driver=driver)) - return content.getvalue() - - @abc.abstractmethod ---- a/cloudinit/net/udev.py -+++ b/cloudinit/net/udev.py -@@ -23,7 +23,7 @@ def compose_udev_setting(key, value): - return '%s="%s"' % (key, value) - - --def generate_udev_rule(interface, mac): -+def generate_udev_rule(interface, mac, driver=None): - """Return a udev rule to set the name of network interface with `mac`. - - The rule ends up as a single line looking something like: -@@ -31,10 +31,13 @@ def generate_udev_rule(interface, mac): - SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", - ATTR{address}="ff:ee:dd:cc:bb:aa", NAME="eth0" - """ -+ if not driver: -+ driver = '?*' -+ - rule = ', '.join([ - compose_udev_equality('SUBSYSTEM', 'net'), - compose_udev_equality('ACTION', 'add'), -- compose_udev_equality('DRIVERS', '?*'), -+ compose_udev_equality('DRIVERS', driver), - compose_udev_attr_equality('address', mac), - compose_udev_setting('NAME', interface), - ]) ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -16,6 +16,7 @@ from xml.dom import minidom - import xml.etree.ElementTree as ET - - from cloudinit import log as logging -+from cloudinit import net - from cloudinit import sources - from cloudinit.sources.helpers.azure import get_metadata_from_fabric - from cloudinit import util -@@ -240,7 +241,9 @@ def temporary_hostname(temp_hostname, cf - set_hostname(previous_hostname, hostname_command) - - --class DataSourceAzureNet(sources.DataSource): -+class DataSourceAzure(sources.DataSource): -+ _negotiated = False -+ - def __init__(self, sys_cfg, distro, paths): - sources.DataSource.__init__(self, sys_cfg, distro, paths) - self.seed_dir = os.path.join(paths.seed_dir, 'azure') -@@ -250,6 +253,7 @@ class DataSourceAzureNet(sources.DataSou - util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}), - BUILTIN_DS_CONFIG]) - self.dhclient_lease_file = self.ds_cfg.get('dhclient_lease_file') -+ self._network_config = None - - def __str__(self): - root = sources.DataSource.__str__(self) -@@ -326,6 +330,7 @@ class DataSourceAzureNet(sources.DataSou - if asset_tag != AZURE_CHASSIS_ASSET_TAG: - LOG.debug("Non-Azure DMI asset tag '%s' discovered.", asset_tag) - return False -+ - ddir = self.ds_cfg['data_dir'] - - candidates = [self.seed_dir] -@@ -370,13 +375,14 @@ class DataSourceAzureNet(sources.DataSou - LOG.debug("using files cached in %s", ddir) - - # azure / hyper-v provides random data here -+ # TODO. find the seed on FreeBSD platform -+ # now update ds_cfg to reflect contents pass in config - if not util.is_FreeBSD(): - seed = util.load_file("/sys/firmware/acpi/tables/OEM0", - quiet=True, decode=False) - if seed: - self.metadata['random_seed'] = seed -- # TODO. find the seed on FreeBSD platform -- # now update ds_cfg to reflect contents pass in config -+ - user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {}) - self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg]) - -@@ -384,6 +390,40 @@ class DataSourceAzureNet(sources.DataSou - # the directory to be protected. - write_files(ddir, files, dirmode=0o700) - -+ self.metadata['instance-id'] = util.read_dmi_data('system-uuid') -+ -+ return True -+ -+ def device_name_to_device(self, name): -+ return self.ds_cfg['disk_aliases'].get(name) -+ -+ def get_config_obj(self): -+ return self.cfg -+ -+ def check_instance_id(self, sys_cfg): -+ # quickly (local check only) if self.instance_id is still valid -+ return sources.instance_id_matches_system_uuid(self.get_instance_id()) -+ -+ def setup(self, is_new_instance): -+ if self._negotiated is False: -+ LOG.debug("negotiating for %s (new_instance=%s)", -+ self.get_instance_id(), is_new_instance) -+ fabric_data = self._negotiate() -+ LOG.debug("negotiating returned %s", fabric_data) -+ if fabric_data: -+ self.metadata.update(fabric_data) -+ self._negotiated = True -+ else: -+ LOG.debug("negotiating already done for %s", -+ self.get_instance_id()) -+ -+ def _negotiate(self): -+ """Negotiate with fabric and return data from it. -+ -+ On success, returns a dictionary including 'public_keys'. -+ On failure, returns False. -+ """ -+ - if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN: - self.bounce_network_with_azure_hostname() - -@@ -393,31 +433,64 @@ class DataSourceAzureNet(sources.DataSou - else: - metadata_func = self.get_metadata_from_agent - -+ LOG.debug("negotiating with fabric via agent command %s", -+ self.ds_cfg['agent_command']) - try: - fabric_data = metadata_func() - except Exception as exc: -- LOG.info("Error communicating with Azure fabric; assume we aren't" -- " on Azure.", exc_info=True) -+ LOG.warning( -+ "Error communicating with Azure fabric; You may experience." -+ "connectivity issues.", exc_info=True) - return False -- self.metadata['instance-id'] = util.read_dmi_data('system-uuid') -- self.metadata.update(fabric_data) -- -- return True -- -- def device_name_to_device(self, name): -- return self.ds_cfg['disk_aliases'].get(name) - -- def get_config_obj(self): -- return self.cfg -- -- def check_instance_id(self, sys_cfg): -- # quickly (local check only) if self.instance_id is still valid -- return sources.instance_id_matches_system_uuid(self.get_instance_id()) -+ return fabric_data - - def activate(self, cfg, is_new_instance): - address_ephemeral_resize(is_new_instance=is_new_instance) - return - -+ @property -+ def network_config(self): -+ """Generate a network config like net.generate_fallback_network() with -+ the following execptions. -+ -+ 1. Probe the drivers of the net-devices present and inject them in -+ the network configuration under params: driver: <driver> value -+ 2. If the driver value is 'mlx4_core', the control mode should be -+ set to manual. The device will be later used to build a bond, -+ for now we want to ensure the device gets named but does not -+ break any network configuration -+ """ -+ blacklist = ['mlx4_core'] -+ if not self._network_config: -+ LOG.debug('Azure: generating fallback configuration') -+ # generate a network config, blacklist picking any mlx4_core devs -+ netconfig = net.generate_fallback_config( -+ blacklist_drivers=blacklist, config_driver=True) -+ -+ # if we have any blacklisted devices, update the network_config to -+ # include the device, mac, and driver values, but with no ip -+ # config; this ensures udev rules are generated but won't affect -+ # ip configuration -+ bl_found = 0 -+ for bl_dev in [dev for dev in net.get_devicelist() -+ if net.device_driver(dev) in blacklist]: -+ bl_found += 1 -+ cfg = { -+ 'type': 'physical', -+ 'name': 'vf%d' % bl_found, -+ 'mac_address': net.get_interface_mac(bl_dev), -+ 'params': { -+ 'driver': net.device_driver(bl_dev), -+ 'device_id': net.device_devid(bl_dev), -+ }, -+ } -+ netconfig['config'].append(cfg) -+ -+ self._network_config = netconfig -+ -+ return self._network_config -+ - - def _partitions_on_device(devpath, maxnum=16): - # return a list of tuples (ptnum, path) for each part on devpath -@@ -840,9 +913,12 @@ class NonAzureDataSource(Exception): - pass - - -+# Legacy: Must be present in case we load an old pkl object -+DataSourceAzureNet = DataSourceAzure -+ - # Used to match classes to dependencies - datasources = [ -- (DataSourceAzureNet, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)), -+ (DataSourceAzure, (sources.DEP_FILESYSTEM, )), - ] - - ---- a/cloudinit/sources/__init__.py -+++ b/cloudinit/sources/__init__.py -@@ -251,10 +251,23 @@ class DataSource(object): - def first_instance_boot(self): - return - -+ def setup(self, is_new_instance): -+ """setup(is_new_instance) -+ -+ This is called before user-data and vendor-data have been processed. -+ -+ Unless the datasource has set mode to 'local', then networking -+ per 'fallback' or per 'network_config' will have been written and -+ brought up the OS at this point. -+ """ -+ return -+ - def activate(self, cfg, is_new_instance): - """activate(cfg, is_new_instance) - -- This is called before the init_modules will be called. -+ This is called before the init_modules will be called but after -+ the user-data and vendor-data have been fully processed. -+ - The cfg is fully up to date config, it contains a merged view of - system config, datasource config, user config, vendor config. - It should be used rather than the sys_cfg passed to __init__. ---- a/cloudinit/stages.py -+++ b/cloudinit/stages.py -@@ -362,6 +362,11 @@ class Init(object): - self._store_userdata() - self._store_vendordata() - -+ def setup_datasource(self): -+ if self.datasource is None: -+ raise RuntimeError("Datasource is None, cannot setup.") -+ self.datasource.setup(is_new_instance=self.is_new_instance()) -+ - def activate_datasource(self): - if self.datasource is None: - raise RuntimeError("Datasource is None, cannot activate.") ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -181,13 +181,19 @@ scbus-1 on xpt0 bus 0 - side_effect=_dmi_mocks)), - ]) - -- dsrc = dsaz.DataSourceAzureNet( -+ dsrc = dsaz.DataSourceAzure( - data.get('sys_cfg', {}), distro=None, paths=self.paths) - if agent_command is not None: - dsrc.ds_cfg['agent_command'] = agent_command - - return dsrc - -+ def _get_and_setup(self, dsrc): -+ ret = dsrc.get_data() -+ if ret: -+ dsrc.setup(True) -+ return ret -+ - def xml_equals(self, oxml, nxml): - """Compare two sets of XML to make sure they are equal""" - -@@ -259,7 +265,7 @@ fdescfs /dev/fd fdes - # Return a non-matching asset tag value - nonazure_tag = dsaz.AZURE_CHASSIS_ASSET_TAG + 'X' - m_read_dmi_data.return_value = nonazure_tag -- dsrc = dsaz.DataSourceAzureNet( -+ dsrc = dsaz.DataSourceAzure( - {}, distro=None, paths=self.paths) - self.assertFalse(dsrc.get_data()) - self.assertEqual( -@@ -298,7 +304,7 @@ fdescfs /dev/fd fdes - data = {'ovfcontent': construct_valid_ovf_env(data=odata)} - - dsrc = self._get_ds(data) -- ret = dsrc.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) - self.assertEqual(data['agent_invoked'], cfg['agent_command']) - -@@ -311,7 +317,7 @@ fdescfs /dev/fd fdes - data = {'ovfcontent': construct_valid_ovf_env(data=odata)} - - dsrc = self._get_ds(data) -- ret = dsrc.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) - self.assertEqual(data['agent_invoked'], cfg['agent_command']) - -@@ -321,7 +327,7 @@ fdescfs /dev/fd fdes - 'sys_cfg': sys_cfg} - - dsrc = self._get_ds(data) -- ret = dsrc.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) - self.assertEqual(data['agent_invoked'], '_COMMAND') - -@@ -393,7 +399,7 @@ fdescfs /dev/fd fdes - pubkeys=pubkeys)} - - dsrc = self._get_ds(data, agent_command=['not', '__builtin__']) -- ret = dsrc.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) - for mypk in mypklist: - self.assertIn(mypk, dsrc.cfg['_pubkeys']) -@@ -408,7 +414,7 @@ fdescfs /dev/fd fdes - pubkeys=pubkeys)} - - dsrc = self._get_ds(data, agent_command=['not', '__builtin__']) -- ret = dsrc.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) - - for mypk in mypklist: -@@ -424,7 +430,7 @@ fdescfs /dev/fd fdes - pubkeys=pubkeys)} - - dsrc = self._get_ds(data, agent_command=['not', '__builtin__']) -- ret = dsrc.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) - - for mypk in mypklist: -@@ -518,18 +524,20 @@ fdescfs /dev/fd fdes - dsrc.get_data() - - def test_exception_fetching_fabric_data_doesnt_propagate(self): -- ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) -- ds.ds_cfg['agent_command'] = '__builtin__' -+ """Errors communicating with fabric should warn, but return True.""" -+ dsrc = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) -+ dsrc.ds_cfg['agent_command'] = '__builtin__' - self.get_metadata_from_fabric.side_effect = Exception -- self.assertFalse(ds.get_data()) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - - def test_fabric_data_included_in_metadata(self): -- ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) -- ds.ds_cfg['agent_command'] = '__builtin__' -+ dsrc = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) -+ dsrc.ds_cfg['agent_command'] = '__builtin__' - self.get_metadata_from_fabric.return_value = {'test': 'value'} -- ret = ds.get_data() -+ ret = self._get_and_setup(dsrc) - self.assertTrue(ret) -- self.assertEqual('value', ds.metadata['test']) -+ self.assertEqual('value', dsrc.metadata['test']) - - def test_instance_id_from_dmidecode_used(self): - ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) -@@ -542,6 +550,84 @@ fdescfs /dev/fd fdes - ds.get_data() - self.assertEqual(self.instance_id, ds.metadata['instance-id']) - -+ @mock.patch('cloudinit.net.get_interface_mac') -+ @mock.patch('cloudinit.net.get_devicelist') -+ @mock.patch('cloudinit.net.device_driver') -+ @mock.patch('cloudinit.net.generate_fallback_config') -+ def test_network_config(self, mock_fallback, mock_dd, -+ mock_devlist, mock_get_mac): -+ odata = {'HostName': "myhost", 'UserName': "myuser"} -+ data = {'ovfcontent': construct_valid_ovf_env(data=odata), -+ 'sys_cfg': {}} -+ -+ fallback_config = { -+ 'version': 1, -+ 'config': [{ -+ 'type': 'physical', 'name': 'eth0', -+ 'mac_address': '00:11:22:33:44:55', -+ 'params': {'driver': 'hv_netsvc'}, -+ 'subnets': [{'type': 'dhcp'}], -+ }] -+ } -+ mock_fallback.return_value = fallback_config -+ -+ mock_devlist.return_value = ['eth0'] -+ mock_dd.return_value = ['hv_netsvc'] -+ mock_get_mac.return_value = '00:11:22:33:44:55' -+ -+ dsrc = self._get_ds(data) -+ ret = dsrc.get_data() -+ self.assertTrue(ret) -+ -+ netconfig = dsrc.network_config -+ self.assertEqual(netconfig, fallback_config) -+ mock_fallback.assert_called_with(blacklist_drivers=['mlx4_core'], -+ config_driver=True) -+ -+ @mock.patch('cloudinit.net.get_interface_mac') -+ @mock.patch('cloudinit.net.get_devicelist') -+ @mock.patch('cloudinit.net.device_driver') -+ @mock.patch('cloudinit.net.generate_fallback_config') -+ def test_network_config_blacklist(self, mock_fallback, mock_dd, -+ mock_devlist, mock_get_mac): -+ odata = {'HostName': "myhost", 'UserName': "myuser"} -+ data = {'ovfcontent': construct_valid_ovf_env(data=odata), -+ 'sys_cfg': {}} -+ -+ fallback_config = { -+ 'version': 1, -+ 'config': [{ -+ 'type': 'physical', 'name': 'eth0', -+ 'mac_address': '00:11:22:33:44:55', -+ 'params': {'driver': 'hv_netsvc'}, -+ 'subnets': [{'type': 'dhcp'}], -+ }] -+ } -+ blacklist_config = { -+ 'type': 'physical', -+ 'name': 'eth1', -+ 'mac_address': '00:11:22:33:44:55', -+ 'params': {'driver': 'mlx4_core'} -+ } -+ mock_fallback.return_value = fallback_config -+ -+ mock_devlist.return_value = ['eth0', 'eth1'] -+ mock_dd.side_effect = [ -+ 'hv_netsvc', # list composition, skipped -+ 'mlx4_core', # list composition, match -+ 'mlx4_core', # config get driver name -+ ] -+ mock_get_mac.return_value = '00:11:22:33:44:55' -+ -+ dsrc = self._get_ds(data) -+ ret = dsrc.get_data() -+ self.assertTrue(ret) -+ -+ netconfig = dsrc.network_config -+ expected_config = fallback_config -+ expected_config['config'].append(blacklist_config) -+ self.assertEqual(netconfig, expected_config) -+ - - class TestAzureBounce(TestCase): - -@@ -591,12 +677,18 @@ class TestAzureBounce(TestCase): - if ovfcontent is not None: - populate_dir(os.path.join(self.paths.seed_dir, "azure"), - {'ovf-env.xml': ovfcontent}) -- dsrc = dsaz.DataSourceAzureNet( -+ dsrc = dsaz.DataSourceAzure( - {}, distro=None, paths=self.paths) - if agent_command is not None: - dsrc.ds_cfg['agent_command'] = agent_command - return dsrc - -+ def _get_and_setup(self, dsrc): -+ ret = dsrc.get_data() -+ if ret: -+ dsrc.setup(True) -+ return ret -+ - def get_ovf_env_with_dscfg(self, hostname, cfg): - odata = { - 'HostName': hostname, -@@ -640,17 +732,20 @@ class TestAzureBounce(TestCase): - host_name = 'unchanged-host-name' - self.get_hostname.return_value = host_name - cfg = {'hostname_bounce': {'policy': 'force'}} -- self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg), -- agent_command=['not', '__builtin__']).get_data() -+ dsrc = self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg), -+ agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(1, perform_hostname_bounce.call_count) - - def test_different_hostnames_sets_hostname(self): - expected_hostname = 'azure-expected-host-name' - self.get_hostname.return_value = 'default-host-name' -- self._get_ds( -+ dsrc = self._get_ds( - self.get_ovf_env_with_dscfg(expected_hostname, {}), -- agent_command=['not', '__builtin__'], -- ).get_data() -+ agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(expected_hostname, - self.set_hostname.call_args_list[0][0][0]) - -@@ -659,19 +754,21 @@ class TestAzureBounce(TestCase): - self, perform_hostname_bounce): - expected_hostname = 'azure-expected-host-name' - self.get_hostname.return_value = 'default-host-name' -- self._get_ds( -+ dsrc = self._get_ds( - self.get_ovf_env_with_dscfg(expected_hostname, {}), -- agent_command=['not', '__builtin__'], -- ).get_data() -+ agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(1, perform_hostname_bounce.call_count) - - def test_different_hostnames_sets_hostname_back(self): - initial_host_name = 'default-host-name' - self.get_hostname.return_value = initial_host_name -- self._get_ds( -+ dsrc = self._get_ds( - self.get_ovf_env_with_dscfg('some-host-name', {}), -- agent_command=['not', '__builtin__'], -- ).get_data() -+ agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(initial_host_name, - self.set_hostname.call_args_list[-1][0][0]) - -@@ -681,10 +778,11 @@ class TestAzureBounce(TestCase): - perform_hostname_bounce.side_effect = Exception - initial_host_name = 'default-host-name' - self.get_hostname.return_value = initial_host_name -- self._get_ds( -+ dsrc = self._get_ds( - self.get_ovf_env_with_dscfg('some-host-name', {}), -- agent_command=['not', '__builtin__'], -- ).get_data() -+ agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(initial_host_name, - self.set_hostname.call_args_list[-1][0][0]) - -@@ -695,7 +793,9 @@ class TestAzureBounce(TestCase): - self.get_hostname.return_value = old_hostname - cfg = {'hostname_bounce': {'interface': interface, 'policy': 'force'}} - data = self.get_ovf_env_with_dscfg(hostname, cfg) -- self._get_ds(data, agent_command=['not', '__builtin__']).get_data() -+ dsrc = self._get_ds(data, agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(1, self.subp.call_count) - bounce_env = self.subp.call_args[1]['env'] - self.assertEqual(interface, bounce_env['interface']) -@@ -707,7 +807,9 @@ class TestAzureBounce(TestCase): - dsaz.BUILTIN_DS_CONFIG['hostname_bounce']['command'] = cmd - cfg = {'hostname_bounce': {'policy': 'force'}} - data = self.get_ovf_env_with_dscfg('some-hostname', cfg) -- self._get_ds(data, agent_command=['not', '__builtin__']).get_data() -+ dsrc = self._get_ds(data, agent_command=['not', '__builtin__']) -+ ret = self._get_and_setup(dsrc) -+ self.assertTrue(ret) - self.assertEqual(1, self.subp.call_count) - bounce_args = self.subp.call_args[1]['args'] - self.assertEqual(cmd, bounce_args) -@@ -963,4 +1065,12 @@ class TestCanDevBeReformatted(CiTestCase - self.assertEqual(False, value) - self.assertIn("3 or more", msg.lower()) - -+ -+class TestAzureNetExists(CiTestCase): -+ def test_azure_net_must_exist_for_legacy_objpkl(self): -+ """DataSourceAzureNet must exist for old obj.pkl files -+ that reference it.""" -+ self.assertTrue(hasattr(dsaz, "DataSourceAzureNet")) -+ -+ - # vi: ts=4 expandtab ---- a/tests/unittests/test_datasource/test_common.py -+++ b/tests/unittests/test_datasource/test_common.py -@@ -26,6 +26,7 @@ from cloudinit.sources import DataSource - from .. import helpers as test_helpers - - DEFAULT_LOCAL = [ -+ Azure.DataSourceAzure, - CloudSigma.DataSourceCloudSigma, - ConfigDrive.DataSourceConfigDrive, - DigitalOcean.DataSourceDigitalOcean, -@@ -37,7 +38,6 @@ DEFAULT_LOCAL = [ - - DEFAULT_NETWORK = [ - AltCloud.DataSourceAltCloud, -- Azure.DataSourceAzureNet, - Bigstep.DataSourceBigstep, - CloudStack.DataSourceCloudStack, - DSNone.DataSourceNone, ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -789,38 +789,176 @@ CONFIG_V1_EXPLICIT_LOOPBACK = { - 'subnets': [{'control': 'auto', 'type': 'loopback'}]}, - ]} - -+DEFAULT_DEV_ATTRS = { -+ 'eth1000': { -+ "bridge": False, -+ "carrier": False, -+ "dormant": False, -+ "operstate": "down", -+ "address": "07-1C-C6-75-A4-BE", -+ "device/driver": None, -+ "device/device": None, -+ } -+} -+ - - def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net, -- 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", -- } -- } -+ mock_sys_dev_path, dev_attrs=None): -+ if not dev_attrs: -+ dev_attrs = DEFAULT_DEV_ATTRS -+ -+ mock_get_devicelist.return_value = dev_attrs.keys() - - def fake_read(devname, path, translate=None, - on_enoent=None, on_keyerror=None, - on_einval=None): -- return dev_characteristics[devname][path] -+ return dev_attrs[devname][path] - - mock_read_sys_net.side_effect = fake_read - - def sys_dev_path(devname, path=""): -- return tmp_dir + devname + "/" + path -+ return tmp_dir + "/" + devname + "/" + path - -- for dev in dev_characteristics: -+ for dev in dev_attrs: - os.makedirs(os.path.join(tmp_dir, dev)) - with open(os.path.join(tmp_dir, dev, 'operstate'), 'w') as fh: -- fh.write("down") -+ fh.write(dev_attrs[dev]['operstate']) -+ os.makedirs(os.path.join(tmp_dir, dev, "device")) -+ for key in ['device/driver']: -+ if key in dev_attrs[dev] and dev_attrs[dev][key]: -+ target = dev_attrs[dev][key] -+ link = os.path.join(tmp_dir, dev, key) -+ print('symlink %s -> %s' % (link, target)) -+ os.symlink(target, link) - - mock_sys_dev_path.side_effect = sys_dev_path - - -+class TestGenerateFallbackConfig(CiTestCase): -+ -+ @mock.patch("cloudinit.net.sys_dev_path") -+ @mock.patch("cloudinit.net.read_sys_net") -+ @mock.patch("cloudinit.net.get_devicelist") -+ def test_device_driver(self, mock_get_devicelist, mock_read_sys_net, -+ mock_sys_dev_path): -+ devices = { -+ 'eth0': { -+ 'bridge': False, 'carrier': False, 'dormant': False, -+ 'operstate': 'down', 'address': '00:11:22:33:44:55', -+ 'device/driver': 'hv_netsvc', 'device/device': '0x3'}, -+ 'eth1': { -+ 'bridge': False, 'carrier': False, 'dormant': False, -+ 'operstate': 'down', 'address': '00:11:22:33:44:55', -+ 'device/driver': 'mlx4_core', 'device/device': '0x7'}, -+ } -+ -+ tmp_dir = self.tmp_dir() -+ _setup_test(tmp_dir, mock_get_devicelist, -+ mock_read_sys_net, mock_sys_dev_path, -+ dev_attrs=devices) -+ -+ network_cfg = net.generate_fallback_config(config_driver=True) -+ ns = network_state.parse_net_config_data(network_cfg, -+ skip_broken=False) -+ -+ render_dir = os.path.join(tmp_dir, "render") -+ os.makedirs(render_dir) -+ -+ # don't set rulepath so eni writes them -+ renderer = eni.Renderer( -+ {'eni_path': 'interfaces', 'netrules_path': 'netrules'}) -+ renderer.render_network_state(ns, render_dir) -+ -+ self.assertTrue(os.path.exists(os.path.join(render_dir, -+ 'interfaces'))) -+ with open(os.path.join(render_dir, 'interfaces')) as fh: -+ contents = fh.read() -+ print(contents) -+ expected = """ -+auto lo -+iface lo inet loopback -+ -+auto eth0 -+iface eth0 inet dhcp -+""" -+ self.assertEqual(expected.lstrip(), contents.lstrip()) -+ -+ self.assertTrue(os.path.exists(os.path.join(render_dir, 'netrules'))) -+ with open(os.path.join(render_dir, 'netrules')) as fh: -+ contents = fh.read() -+ print(contents) -+ expected_rule = [ -+ 'SUBSYSTEM=="net"', -+ 'ACTION=="add"', -+ 'DRIVERS=="hv_netsvc"', -+ 'ATTR{address}=="00:11:22:33:44:55"', -+ 'NAME="eth0"', -+ ] -+ self.assertEqual(", ".join(expected_rule) + '\n', contents.lstrip()) -+ -+ @mock.patch("cloudinit.net.sys_dev_path") -+ @mock.patch("cloudinit.net.read_sys_net") -+ @mock.patch("cloudinit.net.get_devicelist") -+ def test_device_driver_blacklist(self, mock_get_devicelist, -+ mock_read_sys_net, mock_sys_dev_path): -+ devices = { -+ 'eth1': { -+ 'bridge': False, 'carrier': False, 'dormant': False, -+ 'operstate': 'down', 'address': '00:11:22:33:44:55', -+ 'device/driver': 'hv_netsvc', 'device/device': '0x3'}, -+ 'eth0': { -+ 'bridge': False, 'carrier': False, 'dormant': False, -+ 'operstate': 'down', 'address': '00:11:22:33:44:55', -+ 'device/driver': 'mlx4_core', 'device/device': '0x7'}, -+ } -+ -+ tmp_dir = self.tmp_dir() -+ _setup_test(tmp_dir, mock_get_devicelist, -+ mock_read_sys_net, mock_sys_dev_path, -+ dev_attrs=devices) -+ -+ blacklist = ['mlx4_core'] -+ network_cfg = net.generate_fallback_config(blacklist_drivers=blacklist, -+ config_driver=True) -+ ns = network_state.parse_net_config_data(network_cfg, -+ skip_broken=False) -+ -+ render_dir = os.path.join(tmp_dir, "render") -+ os.makedirs(render_dir) -+ -+ # don't set rulepath so eni writes them -+ renderer = eni.Renderer( -+ {'eni_path': 'interfaces', 'netrules_path': 'netrules'}) -+ renderer.render_network_state(ns, render_dir) -+ -+ self.assertTrue(os.path.exists(os.path.join(render_dir, -+ 'interfaces'))) -+ with open(os.path.join(render_dir, 'interfaces')) as fh: -+ contents = fh.read() -+ print(contents) -+ expected = """ -+auto lo -+iface lo inet loopback -+ -+auto eth1 -+iface eth1 inet dhcp -+""" -+ self.assertEqual(expected.lstrip(), contents.lstrip()) -+ -+ self.assertTrue(os.path.exists(os.path.join(render_dir, 'netrules'))) -+ with open(os.path.join(render_dir, 'netrules')) as fh: -+ contents = fh.read() -+ print(contents) -+ expected_rule = [ -+ 'SUBSYSTEM=="net"', -+ 'ACTION=="add"', -+ 'DRIVERS=="hv_netsvc"', -+ 'ATTR{address}=="00:11:22:33:44:55"', -+ 'NAME="eth1"', -+ ] -+ self.assertEqual(", ".join(expected_rule) + '\n', contents.lstrip()) -+ -+ - class TestSysConfigRendering(CiTestCase): - - @mock.patch("cloudinit.net.sys_dev_path") -@@ -1513,6 +1651,118 @@ class TestNetRenderers(CiTestCase): - priority=['sysconfig', 'eni']) - - -+class TestGetInterfaces(CiTestCase): -+ _data = {'bonds': ['bond1'], -+ 'bridges': ['bridge1'], -+ 'vlans': ['bond1.101'], -+ 'own_macs': ['enp0s1', 'enp0s2', 'bridge1-nic', 'bridge1', -+ 'bond1.101', 'lo', 'eth1'], -+ 'macs': {'enp0s1': 'aa:aa:aa:aa:aa:01', -+ 'enp0s2': 'aa:aa:aa:aa:aa:02', -+ 'bond1': 'aa:aa:aa:aa:aa:01', -+ 'bond1.101': 'aa:aa:aa:aa:aa:01', -+ 'bridge1': 'aa:aa:aa:aa:aa:03', -+ 'bridge1-nic': 'aa:aa:aa:aa:aa:03', -+ 'lo': '00:00:00:00:00:00', -+ 'greptap0': '00:00:00:00:00:00', -+ 'eth1': 'aa:aa:aa:aa:aa:01', -+ 'tun0': None}, -+ 'drivers': {'enp0s1': 'virtio_net', -+ 'enp0s2': 'e1000', -+ 'bond1': None, -+ 'bond1.101': None, -+ 'bridge1': None, -+ 'bridge1-nic': None, -+ 'lo': None, -+ 'greptap0': None, -+ 'eth1': 'mlx4_core', -+ 'tun0': None}} -+ data = {} -+ -+ def _se_get_devicelist(self): -+ return list(self.data['devices']) -+ -+ def _se_device_driver(self, name): -+ return self.data['drivers'][name] -+ -+ def _se_device_devid(self, name): -+ return '0x%s' % sorted(list(self.data['drivers'].keys())).index(name) -+ -+ def _se_get_interface_mac(self, name): -+ return self.data['macs'][name] -+ -+ def _se_is_bridge(self, name): -+ return name in self.data['bridges'] -+ -+ def _se_is_vlan(self, name): -+ return name in self.data['vlans'] -+ -+ def _se_interface_has_own_mac(self, name): -+ return name in self.data['own_macs'] -+ -+ def _mock_setup(self): -+ self.data = copy.deepcopy(self._data) -+ self.data['devices'] = set(list(self.data['macs'].keys())) -+ mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge', -+ 'interface_has_own_mac', 'is_vlan', 'device_driver', -+ 'device_devid') -+ self.mocks = {} -+ for n in mocks: -+ m = mock.patch('cloudinit.net.' + n, -+ side_effect=getattr(self, '_se_' + n)) -+ self.addCleanup(m.stop) -+ self.mocks[n] = m.start() -+ -+ def test_gi_includes_duplicate_macs(self): -+ self._mock_setup() -+ ret = net.get_interfaces() -+ -+ self.assertIn('enp0s1', self._se_get_devicelist()) -+ self.assertIn('eth1', self._se_get_devicelist()) -+ found = [ent for ent in ret if 'aa:aa:aa:aa:aa:01' in ent] -+ self.assertEqual(len(found), 2) -+ -+ def test_gi_excludes_any_without_mac_address(self): -+ self._mock_setup() -+ ret = net.get_interfaces() -+ -+ self.assertIn('tun0', self._se_get_devicelist()) -+ found = [ent for ent in ret if 'tun0' in ent] -+ self.assertEqual(len(found), 0) -+ -+ def test_gi_excludes_stolen_macs(self): -+ self._mock_setup() -+ ret = net.get_interfaces() -+ self.mocks['interface_has_own_mac'].assert_has_calls( -+ [mock.call('enp0s1'), mock.call('bond1')], any_order=True) -+ expected = [ -+ ('enp0s2', 'aa:aa:aa:aa:aa:02', 'e1000', '0x5'), -+ ('enp0s1', 'aa:aa:aa:aa:aa:01', 'virtio_net', '0x4'), -+ ('eth1', 'aa:aa:aa:aa:aa:01', 'mlx4_core', '0x6'), -+ ('lo', '00:00:00:00:00:00', None, '0x8'), -+ ('bridge1-nic', 'aa:aa:aa:aa:aa:03', None, '0x3'), -+ ] -+ self.assertEqual(sorted(expected), sorted(ret)) -+ -+ def test_gi_excludes_bridges(self): -+ self._mock_setup() -+ # add a device 'b1', make all return they have their "own mac", -+ # set everything other than 'b1' to be a bridge. -+ # then expect b1 is the only thing left. -+ self.data['macs']['b1'] = 'aa:aa:aa:aa:aa:b1' -+ self.data['drivers']['b1'] = None -+ self.data['devices'].add('b1') -+ self.data['bonds'] = [] -+ self.data['own_macs'] = self.data['devices'] -+ self.data['bridges'] = [f for f in self.data['devices'] if f != "b1"] -+ ret = net.get_interfaces() -+ self.assertEqual([('b1', 'aa:aa:aa:aa:aa:b1', None, '0x0')], ret) -+ self.mocks['is_bridge'].assert_has_calls( -+ [mock.call('bridge1'), mock.call('enp0s1'), mock.call('bond1'), -+ mock.call('b1')], -+ any_order=True) -+ -+ - class TestGetInterfacesByMac(CiTestCase): - _data = {'bonds': ['bond1'], - 'bridges': ['bridge1'], -@@ -1631,4 +1881,202 @@ def _gzip_data(data): - gzfp.close() - return iobuf.getvalue() - -+ -+class TestRenameInterfaces(CiTestCase): -+ -+ @mock.patch('cloudinit.util.subp') -+ def test_rename_all(self, mock_subp): -+ renames = [ -+ ('00:11:22:33:44:55', 'interface0', 'virtio_net', '0x3'), -+ ('00:11:22:33:44:aa', 'interface2', 'virtio_net', '0x5'), -+ ] -+ current_info = { -+ 'ens3': { -+ 'downable': True, -+ 'device_id': '0x3', -+ 'driver': 'virtio_net', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'ens3', -+ 'up': False}, -+ 'ens5': { -+ 'downable': True, -+ 'device_id': '0x5', -+ 'driver': 'virtio_net', -+ 'mac': '00:11:22:33:44:aa', -+ 'name': 'ens5', -+ 'up': False}, -+ } -+ net._rename_interfaces(renames, current_info=current_info) -+ print(mock_subp.call_args_list) -+ mock_subp.assert_has_calls([ -+ mock.call(['ip', 'link', 'set', 'ens3', 'name', 'interface0'], -+ capture=True), -+ mock.call(['ip', 'link', 'set', 'ens5', 'name', 'interface2'], -+ capture=True), -+ ]) -+ -+ @mock.patch('cloudinit.util.subp') -+ def test_rename_no_driver_no_device_id(self, mock_subp): -+ renames = [ -+ ('00:11:22:33:44:55', 'interface0', None, None), -+ ('00:11:22:33:44:aa', 'interface1', None, None), -+ ] -+ current_info = { -+ 'eth0': { -+ 'downable': True, -+ 'device_id': None, -+ 'driver': None, -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth0', -+ 'up': False}, -+ 'eth1': { -+ 'downable': True, -+ 'device_id': None, -+ 'driver': None, -+ 'mac': '00:11:22:33:44:aa', -+ 'name': 'eth1', -+ 'up': False}, -+ } -+ net._rename_interfaces(renames, current_info=current_info) -+ print(mock_subp.call_args_list) -+ mock_subp.assert_has_calls([ -+ mock.call(['ip', 'link', 'set', 'eth0', 'name', 'interface0'], -+ capture=True), -+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'interface1'], -+ capture=True), -+ ]) -+ -+ @mock.patch('cloudinit.util.subp') -+ def test_rename_all_bounce(self, mock_subp): -+ renames = [ -+ ('00:11:22:33:44:55', 'interface0', 'virtio_net', '0x3'), -+ ('00:11:22:33:44:aa', 'interface2', 'virtio_net', '0x5'), -+ ] -+ current_info = { -+ 'ens3': { -+ 'downable': True, -+ 'device_id': '0x3', -+ 'driver': 'virtio_net', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'ens3', -+ 'up': True}, -+ 'ens5': { -+ 'downable': True, -+ 'device_id': '0x5', -+ 'driver': 'virtio_net', -+ 'mac': '00:11:22:33:44:aa', -+ 'name': 'ens5', -+ 'up': True}, -+ } -+ net._rename_interfaces(renames, current_info=current_info) -+ print(mock_subp.call_args_list) -+ mock_subp.assert_has_calls([ -+ mock.call(['ip', 'link', 'set', 'ens3', 'down'], capture=True), -+ mock.call(['ip', 'link', 'set', 'ens3', 'name', 'interface0'], -+ capture=True), -+ mock.call(['ip', 'link', 'set', 'ens5', 'down'], capture=True), -+ mock.call(['ip', 'link', 'set', 'ens5', 'name', 'interface2'], -+ capture=True), -+ mock.call(['ip', 'link', 'set', 'interface0', 'up'], capture=True), -+ mock.call(['ip', 'link', 'set', 'interface2', 'up'], capture=True) -+ ]) -+ -+ @mock.patch('cloudinit.util.subp') -+ def test_rename_duplicate_macs(self, mock_subp): -+ renames = [ -+ ('00:11:22:33:44:55', 'eth0', 'hv_netsvc', '0x3'), -+ ('00:11:22:33:44:55', 'vf1', 'mlx4_core', '0x5'), -+ ] -+ current_info = { -+ 'eth0': { -+ 'downable': True, -+ 'device_id': '0x3', -+ 'driver': 'hv_netsvc', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth0', -+ 'up': False}, -+ 'eth1': { -+ 'downable': True, -+ 'device_id': '0x5', -+ 'driver': 'mlx4_core', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth1', -+ 'up': False}, -+ } -+ net._rename_interfaces(renames, current_info=current_info) -+ print(mock_subp.call_args_list) -+ mock_subp.assert_has_calls([ -+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'vf1'], -+ capture=True), -+ ]) -+ -+ @mock.patch('cloudinit.util.subp') -+ def test_rename_duplicate_macs_driver_no_devid(self, mock_subp): -+ renames = [ -+ ('00:11:22:33:44:55', 'eth0', 'hv_netsvc', None), -+ ('00:11:22:33:44:55', 'vf1', 'mlx4_core', None), -+ ] -+ current_info = { -+ 'eth0': { -+ 'downable': True, -+ 'device_id': '0x3', -+ 'driver': 'hv_netsvc', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth0', -+ 'up': False}, -+ 'eth1': { -+ 'downable': True, -+ 'device_id': '0x5', -+ 'driver': 'mlx4_core', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth1', -+ 'up': False}, -+ } -+ net._rename_interfaces(renames, current_info=current_info) -+ print(mock_subp.call_args_list) -+ mock_subp.assert_has_calls([ -+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'vf1'], -+ capture=True), -+ ]) -+ -+ @mock.patch('cloudinit.util.subp') -+ def test_rename_multi_mac_dups(self, mock_subp): -+ renames = [ -+ ('00:11:22:33:44:55', 'eth0', 'hv_netsvc', '0x3'), -+ ('00:11:22:33:44:55', 'vf1', 'mlx4_core', '0x5'), -+ ('00:11:22:33:44:55', 'vf2', 'mlx4_core', '0x7'), -+ ] -+ current_info = { -+ 'eth0': { -+ 'downable': True, -+ 'device_id': '0x3', -+ 'driver': 'hv_netsvc', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth0', -+ 'up': False}, -+ 'eth1': { -+ 'downable': True, -+ 'device_id': '0x5', -+ 'driver': 'mlx4_core', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth1', -+ 'up': False}, -+ 'eth2': { -+ 'downable': True, -+ 'device_id': '0x7', -+ 'driver': 'mlx4_core', -+ 'mac': '00:11:22:33:44:55', -+ 'name': 'eth2', -+ 'up': False}, -+ } -+ net._rename_interfaces(renames, current_info=current_info) -+ print(mock_subp.call_args_list) -+ mock_subp.assert_has_calls([ -+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'vf1'], -+ capture=True), -+ mock.call(['ip', 'link', 'set', 'eth2', 'name', 'vf2'], -+ capture=True), -+ ]) -+ -+ - # vi: ts=4 expandtab diff --git a/debian/patches/series b/debian/patches/series index 941ab758..7669c82f 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,7 +1,2 @@ azure-use-walinux-agent.patch -cpick-5fb49bac-azure-identify-platform-by-well-known-value-in-chassis ds-identify-behavior-xenial.patch -cpick-003c6678-net-remove-systemd-link-file-writing-from-eni-renderer -cpick-1cd4323b-azure-remove-accidental-duplicate-line-in-merge -cpick-ebc9ecbc-Azure-Add-network-config-Refactor-net-layer-to-handle -cpick-11121fe4-systemd-make-cloud-final.service-run-before-apt-daily |