summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/sources/DataSourceEc2.py38
-rw-r--r--tests/unittests/test_datasource/test_ec2.py97
2 files changed, 135 insertions, 0 deletions
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 8e5f8ee4..07c12bb4 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -57,6 +57,8 @@ class DataSourceEc2(sources.DataSource):
_cloud_platform = None
+ _network_config = None # Used for caching calculated network config v1
+
# Whether we want to get network configuration from the metadata service.
get_network_metadata = False
@@ -279,6 +281,15 @@ class DataSourceEc2(sources.DataSource):
util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT),
cfg)
+ @property
+ def network_config(self):
+ """Return a network config dict for rendering ENI or netplan files."""
+ if self._network_config is None:
+ if self.metadata is not None:
+ self._network_config = convert_ec2_metadata_network_config(
+ self.metadata)
+ return self._network_config
+
def _crawl_metadata(self):
"""Crawl metadata service when available.
@@ -423,6 +434,33 @@ def _collect_platform_data():
return data
+def convert_ec2_metadata_network_config(metadata=None, macs_to_nics=None):
+ """Convert ec2 metadata to network config version 1 data dict.
+
+ @param: metadata: Dictionary of metadata crawled from EC2 metadata url.
+ @param: macs_to_name: Optional dict mac addresses and the nic name. If
+ not provided, get_interfaces_by_mac is called to get it from the OS.
+
+ @return A dict of network config version 1 based on the metadata and macs.
+ """
+ netcfg = {'version': 1, 'config': []}
+ if not macs_to_nics:
+ macs_to_nics = net.get_interfaces_by_mac()
+ macs_metadata = metadata['network']['interfaces']['macs']
+ for mac, nic_name in 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_metadata.get('public-ipv4s'):
+ nic_cfg['subnets'].append({'type': 'dhcp4'})
+ if nic_metadata.get('ipv6s'):
+ nic_cfg['subnets'].append({'type': 'dhcp6'})
+ netcfg['config'].append(nic_cfg)
+ return netcfg
+
+
# Used to match classes to dependencies
datasources = [
(DataSourceEc2Local, (sources.DEP_FILESYSTEM,)), # Run at init-local
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index 33d02619..e1ce6446 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -1,5 +1,6 @@
# This file is part of cloud-init. See LICENSE file for license information.
+import copy
import httpretty
import mock
@@ -195,6 +196,34 @@ class TestEc2(test_helpers.HttprettyTestCase):
return ds
@httpretty.activate
+ def test_network_config_property_returns_version_1_network_data(self):
+ """network_config property returns network version 1 for metadata."""
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
+ md=DEFAULT_METADATA)
+ 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')
+ with mock.patch(patch_path) as m_get_interfaces_by_mac:
+ m_get_interfaces_by_mac.return_value = {mac1: 'eth9'}
+ self.assertEqual(expected, ds.network_config)
+
+ def test_network_config_property_is_cached_in_datasource(self):
+ """network_config property is cached in DataSourceEc2."""
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
+ md=DEFAULT_METADATA)
+ ds._network_config = {'cached': 'data'}
+ self.assertEqual({'cached': 'data'}, ds.network_config)
+
+ @httpretty.activate
@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
def test_valid_platform_with_strict_true(self, m_dhcp):
"""Valid platform data should return true with strict_id true."""
@@ -287,4 +316,72 @@ class TestEc2(test_helpers.HttprettyTestCase):
self.assertIn('Crawl of metadata service took', self.logs.getvalue())
+class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
+
+ def setUp(self):
+ super(TestConvertEc2MetadataNetworkConfig, self).setUp()
+ self.mac1 = '06:17:04:d7:26:09'
+ self.network_metadata = {
+ 'network': {'interfaces': {'macs': {
+ 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'}]}]}
+ self.assertEqual(
+ expected,
+ ec2.convert_ec2_metadata_network_config(
+ self.network_metadata, macs_to_nics))
+
+ def test_convert_ec2_metadata_network_config_handles_only_dhcp6(self):
+ """Config dhcp6 when ipv6s is in metadata for a mac."""
+ macs_to_nics = {self.mac1: 'eth9'}
+ network_metadata_ipv6 = copy.deepcopy(self.network_metadata)
+ nic1_metadata = (
+ network_metadata_ipv6['network']['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'}]}]}
+ self.assertEqual(
+ expected,
+ ec2.convert_ec2_metadata_network_config(
+ network_metadata_ipv6, macs_to_nics))
+
+ def test_convert_ec2_metadata_network_config_handles_dhcp4_and_dhcp6(self):
+ """Config both dhcp4 and dhcp6 when both vpc-ipv6 and ipv4 exists."""
+ macs_to_nics = {self.mac1: 'eth9'}
+ network_metadata_both = copy.deepcopy(self.network_metadata)
+ nic1_metadata = (
+ network_metadata_both['network']['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'}]}]}
+ self.assertEqual(
+ expected,
+ ec2.convert_ec2_metadata_network_config(
+ network_metadata_both, macs_to_nics))
+
+ 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')
+ with mock.patch(patch_path) as m_get_interfaces_by_mac:
+ m_get_interfaces_by_mac.return_value = {self.mac1: 'eth9'}
+ self.assertEqual(
+ expected,
+ ec2.convert_ec2_metadata_network_config(self.network_metadata))
+
# vi: ts=4 expandtab