summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@brickies.net>2017-09-07 09:59:47 -0400
committerScott Moser <smoser@brickies.net>2017-09-07 16:37:44 -0400
commit922c3c5c1a86f2d58e95a328e72b49a3bb234ca8 (patch)
treefc4c6377b9b60e33d3c0c7d7cadaedbd467eedd1
parent409918f9ba83e45e9bc5cc0b6c589e2fc8ae9b60 (diff)
downloadvyos-cloud-init-922c3c5c1a86f2d58e95a328e72b49a3bb234ca8.tar.gz
vyos-cloud-init-922c3c5c1a86f2d58e95a328e72b49a3bb234ca8.zip
Ec2: only attempt to operate at local mode on known platforms.
This change makes the DataSourceEc2Local do nothing unless it is on actual AWS platform. The motivation is twofold: a.) It is generally safer to only make this function available to Ec2 clones that explicitly identify themselves to the guest. (It also gives them a reason to supply identification code to cloud-init.) b.) On non-intel OpenStack platforms ds-identify would enable both the Ec2 and OpenStack sources. That is because there is not good data (such as dmi) to positively identify the platform. Previously that would be fine as OpenStack would run first and be successful. The change to add Ec2Local meant that an Ec2 now runs first. The best case for 'b' would be a slow down as attempts at the Ec2 metadata service time out. The discovered case was worse. Additionally we add a simple check for datatype of 'network' in the metadata before attempting to read it. LP: #1715128
-rw-r--r--cloudinit/sources/DataSourceEc2.py43
-rw-r--r--tests/unittests/test_datasource/test_ec2.py29
2 files changed, 60 insertions, 12 deletions
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 07c12bb4..41367a8b 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -27,6 +27,8 @@ SKIP_METADATA_URL_CODES = frozenset([uhelp.NOT_FOUND])
STRICT_ID_PATH = ("datasource", "Ec2", "strict_id")
STRICT_ID_DEFAULT = "warn"
+_unset = "_unset"
+
class Platforms(object):
ALIYUN = "AliYun"
@@ -57,7 +59,7 @@ class DataSourceEc2(sources.DataSource):
_cloud_platform = None
- _network_config = None # Used for caching calculated network config v1
+ _network_config = _unset # Used for caching calculated network config v1
# Whether we want to get network configuration from the metadata service.
get_network_metadata = False
@@ -284,10 +286,24 @@ class DataSourceEc2(sources.DataSource):
@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)
+ if self._network_config != _unset:
+ return self._network_config
+
+ if self.metadata is None:
+ # this would happen if get_data hadn't been called. leave as _unset
+ LOG.warning(
+ "Unexpected call to network_config when metadata is None.")
+ return None
+
+ result = None
+ net_md = self.metadata.get('network')
+ if isinstance(net_md, dict):
+ result = convert_ec2_metadata_network_config(net_md)
+ else:
+ LOG.warning("unexpected metadata 'network' key not valid: %s",
+ net_md)
+ self._network_config = result
+
return self._network_config
def _crawl_metadata(self):
@@ -321,6 +337,14 @@ class DataSourceEc2Local(DataSourceEc2):
"""
get_network_metadata = True # Get metadata network config if present
+ def get_data(self):
+ supported_platforms = (Platforms.AWS,)
+ if self.cloud_platform not in supported_platforms:
+ LOG.debug("Local Ec2 mode only supported on %s, not %s",
+ supported_platforms, self.cloud_platform)
+ return False
+ return super(DataSourceEc2Local, self).get_data()
+
def read_strict_mode(cfgval, default):
try:
@@ -434,10 +458,13 @@ def _collect_platform_data():
return data
-def convert_ec2_metadata_network_config(metadata=None, macs_to_nics=None):
+def convert_ec2_metadata_network_config(network_md, 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: network_md: 'network' portion of EC2 metadata.
+ generally formed as {"interfaces": {"macs": {}} where
+ 'macs' is a dictionary with mac address as key and contents like:
+ {"device-number": "0", "interface-id": "...", "local-ipv4s": ...}
@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.
@@ -446,7 +473,7 @@ def convert_ec2_metadata_network_config(metadata=None, macs_to_nics=None):
netcfg = {'version': 1, 'config': []}
if not macs_to_nics:
macs_to_nics = net.get_interfaces_by_mac()
- macs_metadata = metadata['network']['interfaces']['macs']
+ macs_metadata = network_md['interfaces']['macs']
for mac, nic_name in macs_to_nics.items():
nic_metadata = macs_metadata.get(mac)
if not nic_metadata:
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index 9fb90483..a7301dbf 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -279,6 +279,27 @@ class TestEc2(test_helpers.HttprettyTestCase):
ret = ds.get_data()
self.assertTrue(ret)
+ def test_ec2_local_returns_false_on_non_aws(self):
+ """DataSourceEc2Local returns False when platform is not AWS."""
+ self.datasource = ec2.DataSourceEc2Local
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md=DEFAULT_METADATA)
+ platform_attrs = [
+ attr for attr in ec2.Platforms.__dict__.keys()
+ if not attr.startswith('__')]
+ for attr_name in platform_attrs:
+ platform_name = getattr(ec2.Platforms, attr_name)
+ if platform_name != 'AWS':
+ ds._cloud_platform = platform_name
+ ret = ds.get_data()
+ self.assertFalse(ret)
+ message = (
+ "Local Ec2 mode only supported on ('AWS',),"
+ ' not {0}'.format(platform_name))
+ self.assertIn(message, self.logs.getvalue())
+
@httpretty.activate
@mock.patch('cloudinit.sources.DataSourceEc2.util.is_FreeBSD')
def test_ec2_local_returns_false_on_bsd(self, m_is_freebsd):
@@ -336,8 +357,8 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
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'}}}}}
+ '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."""
@@ -357,7 +378,7 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
macs_to_nics = {self.mac1: 'eth9'}
network_metadata_ipv6 = copy.deepcopy(self.network_metadata)
nic1_metadata = (
- network_metadata_ipv6['network']['interfaces']['macs'][self.mac1])
+ 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': [
@@ -373,7 +394,7 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
macs_to_nics = {self.mac1: 'eth9'}
network_metadata_both = copy.deepcopy(self.network_metadata)
nic1_metadata = (
- network_metadata_both['network']['interfaces']['macs'][self.mac1])
+ 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',