Merge ~smoser/cloud-init:bug/1715128-ec2-used-on-openstack into cloud-init:master

Proposed by Scott Moser on 2017-09-07
Status: Merged
Approved by: Scott Moser on 2017-09-07
Approved revision: d99b689afe584c44018d55b0c26ebfda4f2a6dfa
Merged at revision: 922c3c5c1a86f2d58e95a328e72b49a3bb234ca8
Proposed branch: ~smoser/cloud-init:bug/1715128-ec2-used-on-openstack
Merge into: cloud-init:master
Diff against target: 152 lines (+60/-12)
2 files modified
cloudinit/sources/DataSourceEc2.py (+35/-8)
tests/unittests/test_datasource/test_ec2.py (+25/-4)
Reviewer Review Type Date Requested Status
Ryan Harper Approve on 2017-09-07
Server Team CI bot continuous-integration Approve on 2017-09-07
Chad Smith 2017-09-07 Approve on 2017-09-07
Review via email: mp+330361@code.launchpad.net

Commit Message

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

To post a comment you must log in.

PASSED: Continuous integration, rev:b4d4faa09cb822ca7124d90da7e78a5af4ad8abd
https://jenkins.ubuntu.com/server/job/cloud-init-ci/264/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/264/rebuild

review: Approve (continuous-integration)
Scott Moser (smoser) wrote :

This will return ec2 to being searched after openstack.
but there are still a couple things i have to think about the stack trace a bit.

a0ef705... by Scott Moser on 2017-09-07

Ec2: return None if in network_config there is no network config.

also here, change the signature of convert_ec2_metadata_network_config
to be reasonable, since it only ever referenced the 'network' entry
in metadata.

still may raise exception and stack trace on invalid data.

2eea4ec... by Scott Moser on 2017-09-07

update tests for function signature change.

c65d1e0... by Scott Moser on 2017-09-07

network_md is mandatory

FAILED: Continuous integration, rev:c65d1e0e7ec6fe4c28fc61ecbfbadfc51cd6b316
https://jenkins.ubuntu.com/server/job/cloud-init-ci/267/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/267/rebuild

review: Needs Fixing (continuous-integration)
5e2ce57... by Scott Moser on 2017-09-07

flake8

Chad Smith (chad.smith) wrote :

+1 with a unit test here http://paste.ubuntu.com/25485368/

review: Approve
d99b689... by Scott Moser on 2017-09-07

add chad's test.

PASSED: Continuous integration, rev:5e2ce5724fafa924c53f3de0bae233b594874496
https://jenkins.ubuntu.com/server/job/cloud-init-ci/268/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/268/rebuild

review: Approve (continuous-integration)

PASSED: Continuous integration, rev:d99b689afe584c44018d55b0c26ebfda4f2a6dfa
https://jenkins.ubuntu.com/server/job/cloud-init-ci/269/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/269/rebuild

review: Approve (continuous-integration)
Ryan Harper (raharper) :
review: Needs Fixing
Scott Moser (smoser) :
Ryan Harper (raharper) wrote :

I'm OK with the current approach of blowing up; but I'd like to see a follow up on validating that the config after conversion has a change of doing something useful.

review: Approve
Scott Moser (smoser) wrote :

ok. given the approve aboev i'm going to pull this

 http://paste.ubuntu.com/25485967/

that patch there has a bit more strict checking, but falling back rathe rthan raising a value error means "hiding" this error.

so i'm leaving it as it is right now.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
2index 07c12bb..41367a8 100644
3--- a/cloudinit/sources/DataSourceEc2.py
4+++ b/cloudinit/sources/DataSourceEc2.py
5@@ -27,6 +27,8 @@ SKIP_METADATA_URL_CODES = frozenset([uhelp.NOT_FOUND])
6 STRICT_ID_PATH = ("datasource", "Ec2", "strict_id")
7 STRICT_ID_DEFAULT = "warn"
8
9+_unset = "_unset"
10+
11
12 class Platforms(object):
13 ALIYUN = "AliYun"
14@@ -57,7 +59,7 @@ class DataSourceEc2(sources.DataSource):
15
16 _cloud_platform = None
17
18- _network_config = None # Used for caching calculated network config v1
19+ _network_config = _unset # Used for caching calculated network config v1
20
21 # Whether we want to get network configuration from the metadata service.
22 get_network_metadata = False
23@@ -284,10 +286,24 @@ class DataSourceEc2(sources.DataSource):
24 @property
25 def network_config(self):
26 """Return a network config dict for rendering ENI or netplan files."""
27- if self._network_config is None:
28- if self.metadata is not None:
29- self._network_config = convert_ec2_metadata_network_config(
30- self.metadata)
31+ if self._network_config != _unset:
32+ return self._network_config
33+
34+ if self.metadata is None:
35+ # this would happen if get_data hadn't been called. leave as _unset
36+ LOG.warning(
37+ "Unexpected call to network_config when metadata is None.")
38+ return None
39+
40+ result = None
41+ net_md = self.metadata.get('network')
42+ if isinstance(net_md, dict):
43+ result = convert_ec2_metadata_network_config(net_md)
44+ else:
45+ LOG.warning("unexpected metadata 'network' key not valid: %s",
46+ net_md)
47+ self._network_config = result
48+
49 return self._network_config
50
51 def _crawl_metadata(self):
52@@ -321,6 +337,14 @@ class DataSourceEc2Local(DataSourceEc2):
53 """
54 get_network_metadata = True # Get metadata network config if present
55
56+ def get_data(self):
57+ supported_platforms = (Platforms.AWS,)
58+ if self.cloud_platform not in supported_platforms:
59+ LOG.debug("Local Ec2 mode only supported on %s, not %s",
60+ supported_platforms, self.cloud_platform)
61+ return False
62+ return super(DataSourceEc2Local, self).get_data()
63+
64
65 def read_strict_mode(cfgval, default):
66 try:
67@@ -434,10 +458,13 @@ def _collect_platform_data():
68 return data
69
70
71-def convert_ec2_metadata_network_config(metadata=None, macs_to_nics=None):
72+def convert_ec2_metadata_network_config(network_md, macs_to_nics=None):
73 """Convert ec2 metadata to network config version 1 data dict.
74
75- @param: metadata: Dictionary of metadata crawled from EC2 metadata url.
76+ @param: network_md: 'network' portion of EC2 metadata.
77+ generally formed as {"interfaces": {"macs": {}} where
78+ 'macs' is a dictionary with mac address as key and contents like:
79+ {"device-number": "0", "interface-id": "...", "local-ipv4s": ...}
80 @param: macs_to_name: Optional dict mac addresses and the nic name. If
81 not provided, get_interfaces_by_mac is called to get it from the OS.
82
83@@ -446,7 +473,7 @@ def convert_ec2_metadata_network_config(metadata=None, macs_to_nics=None):
84 netcfg = {'version': 1, 'config': []}
85 if not macs_to_nics:
86 macs_to_nics = net.get_interfaces_by_mac()
87- macs_metadata = metadata['network']['interfaces']['macs']
88+ macs_metadata = network_md['interfaces']['macs']
89 for mac, nic_name in macs_to_nics.items():
90 nic_metadata = macs_metadata.get(mac)
91 if not nic_metadata:
92diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
93index 9fb9048..a7301db 100644
94--- a/tests/unittests/test_datasource/test_ec2.py
95+++ b/tests/unittests/test_datasource/test_ec2.py
96@@ -279,6 +279,27 @@ class TestEc2(test_helpers.HttprettyTestCase):
97 ret = ds.get_data()
98 self.assertTrue(ret)
99
100+ def test_ec2_local_returns_false_on_non_aws(self):
101+ """DataSourceEc2Local returns False when platform is not AWS."""
102+ self.datasource = ec2.DataSourceEc2Local
103+ ds = self._setup_ds(
104+ platform_data=self.valid_platform_data,
105+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
106+ md=DEFAULT_METADATA)
107+ platform_attrs = [
108+ attr for attr in ec2.Platforms.__dict__.keys()
109+ if not attr.startswith('__')]
110+ for attr_name in platform_attrs:
111+ platform_name = getattr(ec2.Platforms, attr_name)
112+ if platform_name != 'AWS':
113+ ds._cloud_platform = platform_name
114+ ret = ds.get_data()
115+ self.assertFalse(ret)
116+ message = (
117+ "Local Ec2 mode only supported on ('AWS',),"
118+ ' not {0}'.format(platform_name))
119+ self.assertIn(message, self.logs.getvalue())
120+
121 @httpretty.activate
122 @mock.patch('cloudinit.sources.DataSourceEc2.util.is_FreeBSD')
123 def test_ec2_local_returns_false_on_bsd(self, m_is_freebsd):
124@@ -336,8 +357,8 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
125 super(TestConvertEc2MetadataNetworkConfig, self).setUp()
126 self.mac1 = '06:17:04:d7:26:09'
127 self.network_metadata = {
128- 'network': {'interfaces': {'macs': {
129- self.mac1: {'public-ipv4s': '172.31.2.16'}}}}}
130+ 'interfaces': {'macs': {
131+ self.mac1: {'public-ipv4s': '172.31.2.16'}}}}
132
133 def test_convert_ec2_metadata_network_config_skips_absent_macs(self):
134 """Any mac absent from metadata is skipped by network config."""
135@@ -357,7 +378,7 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
136 macs_to_nics = {self.mac1: 'eth9'}
137 network_metadata_ipv6 = copy.deepcopy(self.network_metadata)
138 nic1_metadata = (
139- network_metadata_ipv6['network']['interfaces']['macs'][self.mac1])
140+ network_metadata_ipv6['interfaces']['macs'][self.mac1])
141 nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64'
142 nic1_metadata.pop('public-ipv4s')
143 expected = {'version': 1, 'config': [
144@@ -373,7 +394,7 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
145 macs_to_nics = {self.mac1: 'eth9'}
146 network_metadata_both = copy.deepcopy(self.network_metadata)
147 nic1_metadata = (
148- network_metadata_both['network']['interfaces']['macs'][self.mac1])
149+ network_metadata_both['interfaces']['macs'][self.mac1])
150 nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64'
151 expected = {'version': 1, 'config': [
152 {'mac_address': self.mac1, 'type': 'physical',

Subscribers

People subscribed via source and target branches

to all changes: