diff options
author | Chad Smith <chad.smith@canonical.com> | 2020-03-18 14:01:37 -0600 |
---|---|---|
committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2020-03-18 23:56:09 +0000 |
commit | 358b37c517b26a41072038c35dcb205aca6c339d (patch) | |
tree | 9050ca90fa6aa3435c9c914ed2232770c2f28469 /debian | |
parent | 8b7811a56fbb3201a8ed72efcbd0c85def630a37 (diff) | |
download | cloud-init-git-358b37c517b26a41072038c35dcb205aca6c339d.tar.gz |
20.1-10-g71af48df-0ubuntu2 (patches unapplied)
Imported using git-ubuntu import.
Diffstat (limited to 'debian')
-rw-r--r-- | debian/changelog | 11 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | debian/patches/cpick-6600c642-ec2-render-network-on-all-NICs-and-add-secondary-IPs-as | 706 | ||||
-rw-r--r-- | debian/patches/series | 1 |
4 files changed, 719 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog index 6d09b31b..b4fb48fa 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +cloud-init (20.1-10-g71af48df-0ubuntu2) focal; urgency=medium + + * d/control: add python3-pytest to Build-Depends + - This fixes upstream daily builds. python3-nose is not removed from + Build-Depends because, currently, the Ubuntu package builds will still + use it for testing. + * cherry-pick 6600c642: ec2: render network on all NICs and add + secondary IPs as (LP: #1866930) + + -- Chad Smith <chad.smith@canonical.com> Wed, 18 Mar 2020 14:01:37 -0600 + cloud-init (20.1-10-g71af48df-0ubuntu1) focal; urgency=medium * New upstream snapshot. diff --git a/debian/control b/debian/control index 76930bf1..828558e8 100644 --- a/debian/control +++ b/debian/control @@ -20,6 +20,7 @@ Build-Depends: debhelper (>= 9.20160709), python3-oauthlib, python3-pep8, python3-pyflakes | pyflakes (<< 1.1.0-2), + python3-pytest, python3-requests, python3-serial, python3-setuptools, diff --git a/debian/patches/cpick-6600c642-ec2-render-network-on-all-NICs-and-add-secondary-IPs-as b/debian/patches/cpick-6600c642-ec2-render-network-on-all-NICs-and-add-secondary-IPs-as new file mode 100644 index 00000000..47bf46ab --- /dev/null +++ b/debian/patches/cpick-6600c642-ec2-render-network-on-all-NICs-and-add-secondary-IPs-as @@ -0,0 +1,706 @@ +From 6600c642af3817fe5e0170cb7b4eeac4be3c60eb Mon Sep 17 00:00:00 2001 +From: Chad Smith <chad.smith@canonical.com> +Date: Wed, 18 Mar 2020 13:33:37 -0600 +Subject: [PATCH] ec2: render network on all NICs and add secondary IPs as + static (#114) + +Add support for rendering secondary static IPv4/IPv6 addresses on +any NIC attached to the machine. In order to see secondary IP +addresses in Ec2 IMDS network config, cloud-init now reads metadata +version 2018-09-24. Metadata services which do not support the Ec2 +API version will not get secondary IP addresses configured. + +In order to discover secondary IP address config, cloud-init now +relies on metadata API Parse local-ipv4s, ipv6s, +subnet-ipv4-cidr-block and subnet-ipv6-cidr-block metadata keys to +determine additional IPs and appropriate subnet prefix to set for a +nic. + +Also add the datasource config option apply_full_imds_netork_config +which defaults to true to allow cloud-init to automatically configure +secondary IP addresses. Setting this option to false will tell +cloud-init to avoid setting up secondary IP addresses. + +Also in this branch: + - Shift Ec2 datasource to emit network config v2 instead of v1. + +LP: #1866930 +--- + cloudinit/sources/DataSourceEc2.py | 116 +++++-- + doc/rtd/topics/datasources/ec2.rst | 19 ++ + tests/unittests/test_datasource/test_ec2.py | 329 ++++++++++++++++---- + 3 files changed, 379 insertions(+), 85 deletions(-) + +--- a/cloudinit/sources/DataSourceEc2.py ++++ b/cloudinit/sources/DataSourceEc2.py +@@ -62,7 +62,7 @@ class DataSourceEc2(sources.DataSource): + + # Priority ordered list of additional metadata versions which will be tried + # for extended metadata content. IPv6 support comes in 2016-09-02 +- extended_metadata_versions = ['2016-09-02'] ++ extended_metadata_versions = ['2018-09-24', '2016-09-02'] + + # Setup read_url parameters per get_url_params. + url_max_wait = 120 +@@ -405,13 +405,16 @@ class DataSourceEc2(sources.DataSource): + logfunc=LOG.debug, msg='Re-crawl of metadata service', + func=self.get_data) + +- # Limit network configuration to only the primary/fallback nic + iface = self.fallback_interface +- macs_to_nics = {net.get_interface_mac(iface): iface} + net_md = self.metadata.get('network') + if isinstance(net_md, dict): ++ # SRU_BLOCKER: xenial, bionic and eoan should default ++ # apply_full_imds_network_config to False to retain original ++ # behavior on those releases. + result = convert_ec2_metadata_network_config( +- net_md, macs_to_nics=macs_to_nics, fallback_nic=iface) ++ net_md, fallback_nic=iface, ++ full_network_config=util.get_cfg_option_bool( ++ self.ds_cfg, 'apply_full_imds_network_config', True)) + + # RELEASE_BLOCKER: xenial should drop the below if statement, + # because the issue being addressed doesn't exist pre-netplan. +@@ -719,9 +722,10 @@ def _collect_platform_data(): + return data + + +-def convert_ec2_metadata_network_config(network_md, macs_to_nics=None, +- fallback_nic=None): +- """Convert ec2 metadata to network config version 1 data dict. ++def convert_ec2_metadata_network_config( ++ network_md, macs_to_nics=None, fallback_nic=None, ++ full_network_config=True): ++ """Convert ec2 metadata to network config version 2 data dict. + + @param: network_md: 'network' portion of EC2 metadata. + generally formed as {"interfaces": {"macs": {}} where +@@ -731,28 +735,104 @@ def convert_ec2_metadata_network_config( + not provided, get_interfaces_by_mac is called to get it from the OS. + @param: fallback_nic: Optionally provide the primary nic interface name. + This nic will be guaranteed to minimally have a dhcp4 configuration. ++ @param: full_network_config: Boolean set True to configure all networking ++ presented by IMDS. This includes rendering secondary IPv4 and IPv6 ++ addresses on all NICs and rendering network config on secondary NICs. ++ If False, only the primary nic will be configured and only with dhcp ++ (IPv4/IPv6). + +- @return A dict of network config version 1 based on the metadata and macs. ++ @return A dict of network config version 2 based on the metadata and macs. + """ +- netcfg = {'version': 1, 'config': []} ++ netcfg = {'version': 2, 'ethernets': {}} + if not macs_to_nics: + macs_to_nics = net.get_interfaces_by_mac() + macs_metadata = network_md['interfaces']['macs'] +- for mac, nic_name in macs_to_nics.items(): ++ ++ if not full_network_config: ++ for mac, nic_name in macs_to_nics.items(): ++ if nic_name == fallback_nic: ++ break ++ dev_config = {'dhcp4': True, ++ 'dhcp6': False, ++ 'match': {'macaddress': mac.lower()}, ++ 'set-name': nic_name} ++ nic_metadata = macs_metadata.get(mac) ++ if nic_metadata.get('ipv6s'): # Any IPv6 addresses configured ++ dev_config['dhcp6'] = True ++ netcfg['ethernets'][nic_name] = dev_config ++ return netcfg ++ # Apply network config for all nics and any secondary IPv4/v6 addresses ++ nic_idx = 1 ++ for mac, nic_name in sorted(macs_to_nics.items()): + nic_metadata = macs_metadata.get(mac) + if not nic_metadata: + continue # Not a physical nic represented in metadata +- nic_cfg = {'type': 'physical', 'name': nic_name, 'subnets': []} +- nic_cfg['mac_address'] = mac +- if (nic_name == fallback_nic or nic_metadata.get('public-ipv4s') or +- nic_metadata.get('local-ipv4s')): +- nic_cfg['subnets'].append({'type': 'dhcp4'}) +- if nic_metadata.get('ipv6s'): +- nic_cfg['subnets'].append({'type': 'dhcp6'}) +- netcfg['config'].append(nic_cfg) ++ dhcp_override = {'route-metric': nic_idx * 100} ++ nic_idx += 1 ++ dev_config = {'dhcp4': True, 'dhcp4-overrides': dhcp_override, ++ 'dhcp6': False, ++ 'match': {'macaddress': mac.lower()}, ++ 'set-name': nic_name} ++ if nic_metadata.get('ipv6s'): # Any IPv6 addresses configured ++ dev_config['dhcp6'] = True ++ dev_config['dhcp6-overrides'] = dhcp_override ++ dev_config['addresses'] = get_secondary_addresses(nic_metadata, mac) ++ if not dev_config['addresses']: ++ dev_config.pop('addresses') # Since we found none configured ++ netcfg['ethernets'][nic_name] = dev_config ++ # Remove route-metric dhcp overrides if only one nic configured ++ if len(netcfg['ethernets']) == 1: ++ for nic_name in netcfg['ethernets'].keys(): ++ netcfg['ethernets'][nic_name].pop('dhcp4-overrides') ++ netcfg['ethernets'][nic_name].pop('dhcp6-overrides', None) + return netcfg + + ++def get_secondary_addresses(nic_metadata, mac): ++ """Parse interface-specific nic metadata and return any secondary IPs ++ ++ :return: List of secondary IPv4 or IPv6 addresses to configure on the ++ interface ++ """ ++ ipv4s = nic_metadata.get('local-ipv4s') ++ ipv6s = nic_metadata.get('ipv6s') ++ addresses = [] ++ # In version < 2018-09-24 local_ipv4s or ipv6s is a str with one IP ++ if bool(isinstance(ipv4s, list) and len(ipv4s) > 1): ++ addresses.extend( ++ _get_secondary_addresses( ++ nic_metadata, 'subnet-ipv4-cidr-block', mac, ipv4s, '24')) ++ if bool(isinstance(ipv6s, list) and len(ipv6s) > 1): ++ addresses.extend( ++ _get_secondary_addresses( ++ nic_metadata, 'subnet-ipv6-cidr-block', mac, ipv6s, '128')) ++ return sorted(addresses) ++ ++ ++def _get_secondary_addresses(nic_metadata, cidr_key, mac, ips, default_prefix): ++ """Return list of IP addresses as CIDRs for secondary IPs ++ ++ The CIDR prefix will be default_prefix if cidr_key is absent or not ++ parseable in nic_metadata. ++ """ ++ addresses = [] ++ cidr = nic_metadata.get(cidr_key) ++ prefix = default_prefix ++ if not cidr or len(cidr.split('/')) != 2: ++ ip_type = 'ipv4' if 'ipv4' in cidr_key else 'ipv6' ++ LOG.warning( ++ 'Could not parse %s %s for mac %s. %s network' ++ ' config prefix defaults to /%s', ++ cidr_key, cidr, mac, ip_type, prefix) ++ else: ++ prefix = cidr.split('/')[1] ++ # We know we have > 1 ips for in metadata for this IP type ++ for ip in ips[1:]: ++ addresses.append( ++ '{ip}/{prefix}'.format(ip=ip, prefix=prefix)) ++ return addresses ++ ++ + # Used to match classes to dependencies + datasources = [ + (DataSourceEc2Local, (sources.DEP_FILESYSTEM,)), # Run at init-local +--- a/doc/rtd/topics/datasources/ec2.rst ++++ b/doc/rtd/topics/datasources/ec2.rst +@@ -42,6 +42,7 @@ Note that there are multiple versions of + by default uses **2009-04-04** but newer versions can be supported with + relative ease (newer versions have more data exposed, while maintaining + backward compatibility with the previous versions). ++Version **2016-09-02** is required for secondary IP address support. + + To see which versions are supported from your cloud provider use the following + URL: +@@ -80,6 +81,15 @@ The settings that may be configured are: + * **timeout**: the timeout value provided to urlopen for each individual http + request. This is used both when selecting a metadata_url and when crawling + the metadata service. (default: 50) ++ * **apply_full_imds_network_config**: Boolean (default: True) to allow ++ cloud-init to configure any secondary NICs and secondary IPs described by ++ the metadata service. All network interfaces are configured with DHCP (v4) ++ to obtain an primary IPv4 address and route. Interfaces which have a ++ non-empty 'ipv6s' list will also enable DHCPv6 to obtain a primary IPv6 ++ address and route. The DHCP response (v4 and v6) return an IP that matches ++ the first element of local-ipv4s and ipv6s lists respectively. All ++ additional values (secondary addresses) in the static ip lists will be ++ added to interface. + + An example configuration with the default values is provided below: + +@@ -90,6 +100,7 @@ An example configuration with the defaul + metadata_urls: ["http://169.254.169.254:80", "http://instance-data:8773"] + max_wait: 120 + timeout: 50 ++ apply_full_imds_network_config: true + + Notes + ----- +@@ -102,4 +113,12 @@ Notes + The check for the instance type is performed by is_classic_instance() + method. + ++ * For EC2 instances with multiple network interfaces (NICs) attached, dhcp4 ++ will be enabled to obtain the primary private IPv4 address of those NICs. ++ Wherever dhcp4 or dhcp6 is enabled for a NIC, a dhcp route-metric will be ++ added with the value of ``<device-number + 1> * 100`` to ensure dhcp ++ routes on the primary NIC are preferred to any secondary NICs. ++ For example: the primary NIC will have a DHCP route-metric of 100, ++ the next NIC will be 200. ++ + .. vi: textwidth=78 +--- a/tests/unittests/test_datasource/test_ec2.py ++++ b/tests/unittests/test_datasource/test_ec2.py +@@ -113,6 +113,122 @@ DEFAULT_METADATA = { + "services": {"domain": "amazonaws.com", "partition": "aws"}, + } + ++# collected from api version 2018-09-24/ with ++# python3 -c 'import json ++# from cloudinit.ec2_utils import get_instance_metadata as gm ++# print(json.dumps(gm("2018-09-24"), indent=1, sort_keys=True))' ++ ++NIC1_MD_IPV4_IPV6_MULTI_IP = { ++ "device-number": "0", ++ "interface-id": "eni-0d6335689899ce9cc", ++ "ipv4-associations": { ++ "18.218.219.181": "172.31.44.13" ++ }, ++ "ipv6s": [ ++ "2600:1f16:292:100:c187:593c:4349:136", ++ "2600:1f16:292:100:f153:12a3:c37c:11f9", ++ "2600:1f16:292:100:f152:2222:3333:4444" ++ ], ++ "local-hostname": ("ip-172-31-44-13.us-east-2." ++ "compute.internal"), ++ "local-ipv4s": [ ++ "172.31.44.13", ++ "172.31.45.70" ++ ], ++ "mac": "0a:07:84:3d:6e:38", ++ "owner-id": "329910648901", ++ "public-hostname": ("ec2-18-218-219-181.us-east-2." ++ "compute.amazonaws.com"), ++ "public-ipv4s": "18.218.219.181", ++ "security-group-ids": "sg-0c387755222ba8d2e", ++ "security-groups": "launch-wizard-4", ++ "subnet-id": "subnet-9d7ba0d1", ++ "subnet-ipv4-cidr-block": "172.31.32.0/20", ++ "subnet_ipv6_cidr_blocks": "2600:1f16:292:100::/64", ++ "vpc-id": "vpc-a07f62c8", ++ "vpc-ipv4-cidr-block": "172.31.0.0/16", ++ "vpc-ipv4-cidr-blocks": "172.31.0.0/16", ++ "vpc_ipv6_cidr_blocks": "2600:1f16:292:100::/56" ++} ++ ++NIC2_MD = { ++ "device_number": "1", ++ "interface_id": "eni-043cdce36ded5e79f", ++ "local_hostname": "ip-172-31-47-221.us-east-2.compute.internal", ++ "local_ipv4s": "172.31.47.221", ++ "mac": "0a:75:69:92:e2:16", ++ "owner_id": "329910648901", ++ "security_group_ids": "sg-0d68fef37d8cc9b77", ++ "security_groups": "launch-wizard-17", ++ "subnet_id": "subnet-9d7ba0d1", ++ "subnet_ipv4_cidr_block": "172.31.32.0/20", ++ "vpc_id": "vpc-a07f62c8", ++ "vpc_ipv4_cidr_block": "172.31.0.0/16", ++ "vpc_ipv4_cidr_blocks": "172.31.0.0/16" ++} ++ ++SECONDARY_IP_METADATA_2018_09_24 = { ++ "ami-id": "ami-0986c2ac728528ac2", ++ "ami-launch-index": "0", ++ "ami-manifest-path": "(unknown)", ++ "block-device-mapping": { ++ "ami": "/dev/sda1", ++ "root": "/dev/sda1" ++ }, ++ "events": { ++ "maintenance": { ++ "history": "[]", ++ "scheduled": "[]" ++ } ++ }, ++ "hostname": "ip-172-31-44-13.us-east-2.compute.internal", ++ "identity-credentials": { ++ "ec2": { ++ "info": { ++ "AccountId": "329910648901", ++ "Code": "Success", ++ "LastUpdated": "2019-07-06T14:22:56Z" ++ } ++ } ++ }, ++ "instance-action": "none", ++ "instance-id": "i-069e01e8cc43732f8", ++ "instance-type": "t2.micro", ++ "local-hostname": "ip-172-31-44-13.us-east-2.compute.internal", ++ "local-ipv4": "172.31.44.13", ++ "mac": "0a:07:84:3d:6e:38", ++ "metrics": { ++ "vhostmd": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ++ }, ++ "network": { ++ "interfaces": { ++ "macs": { ++ "0a:07:84:3d:6e:38": NIC1_MD_IPV4_IPV6_MULTI_IP, ++ } ++ } ++ }, ++ "placement": { ++ "availability-zone": "us-east-2c" ++ }, ++ "profile": "default-hvm", ++ "public-hostname": ( ++ "ec2-18-218-219-181.us-east-2.compute.amazonaws.com"), ++ "public-ipv4": "18.218.219.181", ++ "public-keys": { ++ "yourkeyname,e": [ ++ "ssh-rsa AAAAW...DZ yourkeyname" ++ ] ++ }, ++ "reservation-id": "r-09b4917135cdd33be", ++ "security-groups": "launch-wizard-4", ++ "services": { ++ "domain": "amazonaws.com", ++ "partition": "aws" ++ } ++} ++ ++M_PATH_NET = 'cloudinit.sources.DataSourceEc2.net.' ++ + + def _register_ssh_keys(rfunc, base_url, keys_data): + """handle ssh key inconsistencies. +@@ -267,30 +383,23 @@ class TestEc2(test_helpers.HttprettyTest + register_mock_metaserver(instance_id_url, None) + return ds + +- def test_network_config_property_returns_version_1_network_data(self): +- """network_config property returns network version 1 for metadata. +- +- Only one device is configured even when multiple exist in metadata. +- """ ++ def test_network_config_property_returns_version_2_network_data(self): ++ """network_config property returns network version 2 for metadata""" + ds = self._setup_ds( + platform_data=self.valid_platform_data, + sys_cfg={'datasource': {'Ec2': {'strict_id': True}}}, + md={'md': DEFAULT_METADATA}) +- find_fallback_path = ( +- 'cloudinit.sources.DataSourceEc2.net.find_fallback_nic') ++ find_fallback_path = M_PATH_NET + 'find_fallback_nic' + with mock.patch(find_fallback_path) as m_find_fallback: + m_find_fallback.return_value = 'eth9' + ds.get_data() + + mac1 = '06:17:04:d7:26:09' # Defined in DEFAULT_METADATA +- expected = {'version': 1, 'config': [ +- {'mac_address': '06:17:04:d7:26:09', 'name': 'eth9', +- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}], +- 'type': 'physical'}]} +- patch_path = ( +- 'cloudinit.sources.DataSourceEc2.net.get_interfaces_by_mac') +- get_interface_mac_path = ( +- 'cloudinit.sources.DataSourceEc2.net.get_interface_mac') ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': '06:17:04:d7:26:09'}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': True}}} ++ patch_path = M_PATH_NET + 'get_interfaces_by_mac' ++ get_interface_mac_path = M_PATH_NET + 'get_interface_mac' + with mock.patch(patch_path) as m_get_interfaces_by_mac: + with mock.patch(find_fallback_path) as m_find_fallback: + with mock.patch(get_interface_mac_path) as m_get_mac: +@@ -299,30 +408,59 @@ class TestEc2(test_helpers.HttprettyTest + m_get_mac.return_value = mac1 + self.assertEqual(expected, ds.network_config) + +- def test_network_config_property_set_dhcp4_on_private_ipv4(self): +- """network_config property configures dhcp4 on private ipv4 nics. ++ def test_network_config_property_set_dhcp4(self): ++ """network_config property configures dhcp4 on nics with local-ipv4s. + +- Only one device is configured even when multiple exist in metadata. ++ Only one device is configured based on get_interfaces_by_mac even when ++ multiple MACs exist in metadata. + """ + ds = self._setup_ds( + platform_data=self.valid_platform_data, + sys_cfg={'datasource': {'Ec2': {'strict_id': True}}}, + md={'md': DEFAULT_METADATA}) +- find_fallback_path = ( +- 'cloudinit.sources.DataSourceEc2.net.find_fallback_nic') ++ find_fallback_path = M_PATH_NET + 'find_fallback_nic' + with mock.patch(find_fallback_path) as m_find_fallback: + m_find_fallback.return_value = 'eth9' + ds.get_data() + + mac1 = '06:17:04:d7:26:0A' # IPv4 only in DEFAULT_METADATA +- expected = {'version': 1, 'config': [ +- {'mac_address': '06:17:04:d7:26:0A', 'name': 'eth9', +- 'subnets': [{'type': 'dhcp4'}], +- 'type': 'physical'}]} +- patch_path = ( +- 'cloudinit.sources.DataSourceEc2.net.get_interfaces_by_mac') +- get_interface_mac_path = ( +- 'cloudinit.sources.DataSourceEc2.net.get_interface_mac') ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': mac1.lower()}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': False}}} ++ patch_path = M_PATH_NET + 'get_interfaces_by_mac' ++ get_interface_mac_path = M_PATH_NET + 'get_interface_mac' ++ with mock.patch(patch_path) as m_get_interfaces_by_mac: ++ with mock.patch(find_fallback_path) as m_find_fallback: ++ with mock.patch(get_interface_mac_path) as m_get_mac: ++ m_get_interfaces_by_mac.return_value = {mac1: 'eth9'} ++ m_find_fallback.return_value = 'eth9' ++ m_get_mac.return_value = mac1 ++ self.assertEqual(expected, ds.network_config) ++ ++ def test_network_config_property_secondary_private_ips(self): ++ """network_config property configures any secondary ipv4 addresses. ++ ++ Only one device is configured based on get_interfaces_by_mac even when ++ multiple MACs exist in metadata. ++ """ ++ ds = self._setup_ds( ++ platform_data=self.valid_platform_data, ++ sys_cfg={'datasource': {'Ec2': {'strict_id': True}}}, ++ md={'md': SECONDARY_IP_METADATA_2018_09_24}) ++ find_fallback_path = M_PATH_NET + 'find_fallback_nic' ++ with mock.patch(find_fallback_path) as m_find_fallback: ++ m_find_fallback.return_value = 'eth9' ++ ds.get_data() ++ ++ mac1 = '0a:07:84:3d:6e:38' # 1 secondary IPv4 and 2 secondary IPv6 ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': mac1}, 'set-name': 'eth9', ++ 'addresses': ['172.31.45.70/20', ++ '2600:1f16:292:100:f152:2222:3333:4444/128', ++ '2600:1f16:292:100:f153:12a3:c37c:11f9/128'], ++ 'dhcp4': True, 'dhcp6': True}}} ++ patch_path = M_PATH_NET + 'get_interfaces_by_mac' ++ get_interface_mac_path = M_PATH_NET + 'get_interface_mac' + with mock.patch(patch_path) as m_get_interfaces_by_mac: + with mock.patch(find_fallback_path) as m_find_fallback: + with mock.patch(get_interface_mac_path) as m_get_mac: +@@ -358,21 +496,18 @@ class TestEc2(test_helpers.HttprettyTest + register_mock_metaserver( + 'http://169.254.169.254/2009-04-04/meta-data/', DEFAULT_METADATA) + mac1 = '06:17:04:d7:26:09' # Defined in DEFAULT_METADATA +- get_interface_mac_path = ( +- 'cloudinit.sources.DataSourceEc2.net.get_interface_mac') ++ get_interface_mac_path = M_PATH_NET + 'get_interfaces_by_mac' + ds.fallback_nic = 'eth9' +- with mock.patch(get_interface_mac_path) as m_get_interface_mac: +- m_get_interface_mac.return_value = mac1 ++ with mock.patch(get_interface_mac_path) as m_get_interfaces_by_mac: ++ m_get_interfaces_by_mac.return_value = {mac1: 'eth9'} + nc = ds.network_config # Will re-crawl network metadata + self.assertIsNotNone(nc) + self.assertIn( + 'Refreshing stale metadata from prior to upgrade', + self.logs.getvalue()) +- expected = {'version': 1, 'config': [ +- {'mac_address': '06:17:04:d7:26:09', +- 'name': 'eth9', +- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}], +- 'type': 'physical'}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': True}}} + self.assertEqual(expected, ds.network_config) + + def test_ec2_get_instance_id_refreshes_identity_on_upgrade(self): +@@ -491,7 +626,7 @@ class TestEc2(test_helpers.HttprettyTest + logs_with_redacted = [log for log in all_logs if REDACT_TOK in log] + logs_with_token = [log for log in all_logs if 'API-TOKEN' in log] + self.assertEqual(1, len(logs_with_redacted_ttl)) +- self.assertEqual(79, len(logs_with_redacted)) ++ self.assertEqual(81, len(logs_with_redacted)) + self.assertEqual(0, len(logs_with_token)) + + @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery') +@@ -612,6 +747,44 @@ class TestEc2(test_helpers.HttprettyTest + self.assertIn('Crawl of metadata service took', self.logs.getvalue()) + + ++class TestGetSecondaryAddresses(test_helpers.CiTestCase): ++ ++ mac = '06:17:04:d7:26:ff' ++ with_logs = True ++ ++ def test_md_with_no_secondary_addresses(self): ++ """Empty list is returned when nic metadata contains no secondary ip""" ++ self.assertEqual([], ec2.get_secondary_addresses(NIC2_MD, self.mac)) ++ ++ def test_md_with_secondary_v4_and_v6_addresses(self): ++ """All secondary addresses are returned from nic metadata""" ++ self.assertEqual( ++ ['172.31.45.70/20', '2600:1f16:292:100:f152:2222:3333:4444/128', ++ '2600:1f16:292:100:f153:12a3:c37c:11f9/128'], ++ ec2.get_secondary_addresses(NIC1_MD_IPV4_IPV6_MULTI_IP, self.mac)) ++ ++ def test_invalid_ipv4_ipv6_cidr_metadata_logged_with_defaults(self): ++ """Any invalid subnet-ipv(4|6)-cidr-block values use defaults""" ++ invalid_cidr_md = copy.deepcopy(NIC1_MD_IPV4_IPV6_MULTI_IP) ++ invalid_cidr_md['subnet-ipv4-cidr-block'] = "something-unexpected" ++ invalid_cidr_md['subnet-ipv6-cidr-block'] = "not/sure/what/this/is" ++ self.assertEqual( ++ ['172.31.45.70/24', '2600:1f16:292:100:f152:2222:3333:4444/128', ++ '2600:1f16:292:100:f153:12a3:c37c:11f9/128'], ++ ec2.get_secondary_addresses(invalid_cidr_md, self.mac)) ++ expected_logs = [ ++ "WARNING: Could not parse subnet-ipv4-cidr-block" ++ " something-unexpected for mac 06:17:04:d7:26:ff." ++ " ipv4 network config prefix defaults to /24", ++ "WARNING: Could not parse subnet-ipv6-cidr-block" ++ " not/sure/what/this/is for mac 06:17:04:d7:26:ff." ++ " ipv6 network config prefix defaults to /128" ++ ] ++ logs = self.logs.getvalue() ++ for log in expected_logs: ++ self.assertIn(log, logs) ++ ++ + class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase): + + def setUp(self): +@@ -619,16 +792,16 @@ class TestConvertEc2MetadataNetworkConfi + self.mac1 = '06:17:04:d7:26:09' + self.network_metadata = { + 'interfaces': {'macs': { +- self.mac1: {'public-ipv4s': '172.31.2.16'}}}} ++ self.mac1: {'mac': self.mac1, 'public-ipv4s': '172.31.2.16'}}}} + + def test_convert_ec2_metadata_network_config_skips_absent_macs(self): + """Any mac absent from metadata is skipped by network config.""" + macs_to_nics = {self.mac1: 'eth9', 'DE:AD:BE:EF:FF:FF': 'vitualnic2'} + + # DE:AD:BE:EF:FF:FF represented by OS but not in metadata +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', 'subnets': [{'type': 'dhcp4'}]}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': False}}} + self.assertEqual( + expected, + ec2.convert_ec2_metadata_network_config( +@@ -642,15 +815,15 @@ class TestConvertEc2MetadataNetworkConfi + network_metadata_ipv6['interfaces']['macs'][self.mac1]) + nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64' + nic1_metadata.pop('public-ipv4s') +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', 'subnets': [{'type': 'dhcp6'}]}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': True}}} + self.assertEqual( + expected, + ec2.convert_ec2_metadata_network_config( + network_metadata_ipv6, macs_to_nics)) + +- def test_convert_ec2_metadata_network_config_handles_local_dhcp4(self): ++ def test_convert_ec2_metadata_network_config_local_only_dhcp4(self): + """Config dhcp4 when there are no public addresses in public-ipv4s.""" + macs_to_nics = {self.mac1: 'eth9'} + network_metadata_ipv6 = copy.deepcopy(self.network_metadata) +@@ -658,9 +831,9 @@ class TestConvertEc2MetadataNetworkConfi + network_metadata_ipv6['interfaces']['macs'][self.mac1]) + nic1_metadata['local-ipv4s'] = '172.3.3.15' + nic1_metadata.pop('public-ipv4s') +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', 'subnets': [{'type': 'dhcp4'}]}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': False}}} + self.assertEqual( + expected, + ec2.convert_ec2_metadata_network_config( +@@ -675,16 +848,16 @@ class TestConvertEc2MetadataNetworkConfi + nic1_metadata['public-ipv4s'] = '' + + # When no ipv4 or ipv6 content but fallback_nic set, set dhcp4 config. +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', 'subnets': [{'type': 'dhcp4'}]}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': False}}} + self.assertEqual( + expected, + ec2.convert_ec2_metadata_network_config( + network_metadata_ipv6, macs_to_nics, fallback_nic='eth9')) + + def test_convert_ec2_metadata_network_config_handles_local_v4_and_v6(self): +- """When dhcp6 is public and dhcp4 is set to local enable both.""" ++ """When ipv6s and local-ipv4s are non-empty, enable dhcp6 and dhcp4.""" + macs_to_nics = {self.mac1: 'eth9'} + network_metadata_both = copy.deepcopy(self.network_metadata) + nic1_metadata = ( +@@ -692,10 +865,35 @@ class TestConvertEc2MetadataNetworkConfi + nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64' + nic1_metadata.pop('public-ipv4s') + nic1_metadata['local-ipv4s'] = '10.0.0.42' # Local ipv4 only on vpc +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', +- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}]}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': True}}} ++ self.assertEqual( ++ expected, ++ ec2.convert_ec2_metadata_network_config( ++ network_metadata_both, macs_to_nics)) ++ ++ def test_convert_ec2_metadata_network_config_handles_multiple_nics(self): ++ """DHCP route-metric increases on secondary NICs for IPv4 and IPv6.""" ++ mac2 = '06:17:04:d7:26:0a' ++ macs_to_nics = {self.mac1: 'eth9', mac2: 'eth10'} ++ network_metadata_both = copy.deepcopy(self.network_metadata) ++ # Add 2nd nic info ++ network_metadata_both['interfaces']['macs'][mac2] = NIC2_MD ++ nic1_metadata = ( ++ network_metadata_both['interfaces']['macs'][self.mac1]) ++ nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64' ++ nic1_metadata.pop('public-ipv4s') # No public-ipv4 IPs in cfg ++ nic1_metadata['local-ipv4s'] = '10.0.0.42' # Local ipv4 only on vpc ++ expected = {'version': 2, 'ethernets': { ++ 'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp4-overrides': {'route-metric': 100}, ++ 'dhcp6': True, 'dhcp6-overrides': {'route-metric': 100}}, ++ 'eth10': { ++ 'match': {'macaddress': mac2}, 'set-name': 'eth10', ++ 'dhcp4': True, 'dhcp4-overrides': {'route-metric': 200}, ++ 'dhcp6': False}}} + self.assertEqual( + expected, + ec2.convert_ec2_metadata_network_config( +@@ -708,10 +906,9 @@ class TestConvertEc2MetadataNetworkConfi + nic1_metadata = ( + network_metadata_both['interfaces']['macs'][self.mac1]) + nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64' +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', +- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}]}]} ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9', ++ 'dhcp4': True, 'dhcp6': True}}} + self.assertEqual( + expected, + ec2.convert_ec2_metadata_network_config( +@@ -719,12 +916,10 @@ class TestConvertEc2MetadataNetworkConfi + + def test_convert_ec2_metadata_gets_macs_from_get_interfaces_by_mac(self): + """Convert Ec2 Metadata calls get_interfaces_by_mac by default.""" +- expected = {'version': 1, 'config': [ +- {'mac_address': self.mac1, 'type': 'physical', +- 'name': 'eth9', +- 'subnets': [{'type': 'dhcp4'}]}]} +- patch_path = ( +- 'cloudinit.sources.DataSourceEc2.net.get_interfaces_by_mac') ++ expected = {'version': 2, 'ethernets': {'eth9': { ++ 'match': {'macaddress': self.mac1}, ++ 'set-name': 'eth9', 'dhcp4': True, 'dhcp6': False}}} ++ patch_path = M_PATH_NET + 'get_interfaces_by_mac' + with mock.patch(patch_path) as m_get_interfaces_by_mac: + m_get_interfaces_by_mac.return_value = {self.mac1: 'eth9'} + self.assertEqual( diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 00000000..d65a3307 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +cpick-6600c642-ec2-render-network-on-all-NICs-and-add-secondary-IPs-as |