Merge ~ajorgens/cloud-init:instance-identity into cloud-init:master

Proposed by Andrew Jorgensen
Status: Merged
Approved by: Scott Moser
Approved revision: 0aa9fe8c552cc95335fc112ba2a9d2e52dea84ba
Merged at revision: 703241a3c50f2cfec21e7c8e90616c3378ebbea2
Proposed branch: ~ajorgens/cloud-init:instance-identity
Merge into: cloud-init:master
Diff against target: 198 lines (+78/-22)
4 files modified
cloudinit/distros/__init__.py (+7/-8)
cloudinit/ec2_utils.py (+30/-9)
cloudinit/sources/DataSourceEc2.py (+25/-5)
tests/unittests/test_datasource/test_aliyun.py (+16/-0)
Reviewer Review Type Date Requested Status
Chad Smith Approve
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+329657@code.launchpad.net

Commit message

ec2: Use instance-identity doc for region and instance-id

The instance identity document is a better source for region information, partly because region isn't actually in meta-data at all, only availability-zone, which happens to be named similarly.

http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:0af07e16ac1be5a98f83176012e5e1af3a6d9b2d
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~ajorgens/cloud-init/+git/cloud-init/+merge/329657/+edit-commit-message

https://jenkins.ubuntu.com/server/job/cloud-init-ci/212/
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/212/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I'm not opposed to this in principal, I just have a few concerns. We need to make sure we continue to work on "clones" of ec2 and do not significantly affect them negatively. Chad's recent work to use a newer version of the metadata service and also our identifying the platform (Platforms.AWS) might allow us to more aggressively take advantage of "Genuine AWS".

Someo comments in line.

At very least we need some information and more thought at this point.

review: Needs Information
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/270/
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/270/rebuild

review: Approve (continuous-integration)
Revision history for this message
Andrew Jorgensen (ajorgens) wrote :

I made a point of ensuring that the unit tests for Aliyun still worked. Lars had a PR for better mocking of meta-data service that might be helpful here.

If you have advice on which clones this needs to be tested against I'm happy to try.

Revision history for this message
Scott Moser (smoser) :
Revision history for this message
Andrew Jorgensen (ajorgens) :
Revision history for this message
Scott Moser (smoser) wrote :

Can you adjust this to search under ec2 only if on Platforms.aws ?

see comment.

0aa9fe8... by Andrew Jorgensen

ec2: Use instance-identity doc for region and instance-id

The instance identity document is a better source for region information,
partly because region isn't actually in meta-data at all, only
availability-zone, which happens to be named similarly.

Reviewed-by: Ethan Faust <email address hidden>
Reviewed-by: Cyle Riggs <email address hidden>
Reviewed-by: Tom Kirchner <email address hidden>
Reviewed-by: Matt Nierzwicki <email address hidden>
[<email address hidden>: rebase onto 0.7.9]
[<email address hidden>: changes per merge proposal discussions]

Revision history for this message
Andrew Jorgensen (ajorgens) wrote :

Is this what you're looking for?

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:0aa9fe8c552cc95335fc112ba2a9d2e52dea84ba
https://jenkins.ubuntu.com/server/job/cloud-init-ci/549/
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/549/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) :
Revision history for this message
Scott Moser (smoser) wrote :

I approve.
Ryan was just suggesting your use of the word 'current' be replaced with 'current as of 2017-12-11' or something.

i'm capable of doing that change.

review: Approve
Revision history for this message
Chad Smith (chad.smith) wrote :

+1 on this branch, tested on

As a follow-up. I'd like to see us persist this new dynamic/identity metadata route in the datasource's self.metadata dict because that is written to instance-data.json on which other consumers will eventually rely. The datasource should be persisting any metadata on which it relies to introspect the platform so that others could consume that same data.

review: Approve
Revision history for this message
Chad Smith (chad.smith) wrote :

sorry tested on non-ec2 openstack 'lookalike' platform as well as Ec2 proper instances. Looks good and good fallback configuration if instance-identity doesn't exist as expected.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
2index d5becd1..e576c24 100755
3--- a/cloudinit/distros/__init__.py
4+++ b/cloudinit/distros/__init__.py
5@@ -45,6 +45,10 @@ OSFAMILIES = {
6
7 LOG = logging.getLogger(__name__)
8
9+# This is a best guess regex, based on current EC2 AZs, it could break when
10+# Amazon adds new regions and new AZs.
11+_EC2_AZ_RE = re.compile('^[a-z][a-z]-(?:[a-z]+-)+[0-9][a-z]$')
12+
13
14 @six.add_metaclass(abc.ABCMeta)
15 class Distro(object):
16@@ -686,18 +690,13 @@ def _get_package_mirror_info(mirror_info, data_source=None,
17 if not mirror_info:
18 mirror_info = {}
19
20- # ec2 availability zones are named cc-direction-[0-9][a-d] (us-east-1b)
21- # the region is us-east-1. so region = az[0:-1]
22- directions_re = '|'.join([
23- 'central', 'east', 'north', 'northeast', 'northwest',
24- 'south', 'southeast', 'southwest', 'west'])
25- ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" % directions_re)
26-
27 subst = {}
28 if data_source and data_source.availability_zone:
29 subst['availability_zone'] = data_source.availability_zone
30
31- if re.match(ec2_az_re, data_source.availability_zone):
32+ # ec2 availability zones are named cc-direction-[0-9][a-d] (us-east-1b)
33+ # the region is us-east-1. so region = az[0:-1]
34+ if _EC2_AZ_RE.match(data_source.availability_zone):
35 subst['ec2_region'] = "%s" % data_source.availability_zone[0:-1]
36
37 if data_source and data_source.region:
38diff --git a/cloudinit/ec2_utils.py b/cloudinit/ec2_utils.py
39index 723d6bd..d6c61e4 100644
40--- a/cloudinit/ec2_utils.py
41+++ b/cloudinit/ec2_utils.py
42@@ -1,6 +1,8 @@
43 # Copyright (C) 2012 Yahoo! Inc.
44+# Copyright (C) 2014 Amazon.com, Inc. or its affiliates.
45 #
46 # Author: Joshua Harlow <harlowja@yahoo-inc.com>
47+# Author: Andrew Jorgensen <ajorgens@amazon.com>
48 #
49 # This file is part of cloud-init. See LICENSE file for license information.
50
51@@ -164,14 +166,11 @@ def get_instance_userdata(api_version='latest',
52 return user_data
53
54
55-def get_instance_metadata(api_version='latest',
56- metadata_address='http://169.254.169.254',
57- ssl_details=None, timeout=5, retries=5,
58- leaf_decoder=None):
59- md_url = url_helper.combine_url(metadata_address, api_version)
60- # Note, 'meta-data' explicitly has trailing /.
61- # this is required for CloudStack (LP: #1356855)
62- md_url = url_helper.combine_url(md_url, 'meta-data/')
63+def _get_instance_metadata(tree, api_version='latest',
64+ metadata_address='http://169.254.169.254',
65+ ssl_details=None, timeout=5, retries=5,
66+ leaf_decoder=None):
67+ md_url = url_helper.combine_url(metadata_address, api_version, tree)
68 caller = functools.partial(util.read_file_or_url,
69 ssl_details=ssl_details, timeout=timeout,
70 retries=retries)
71@@ -189,7 +188,29 @@ def get_instance_metadata(api_version='latest',
72 md = {}
73 return md
74 except Exception:
75- util.logexc(LOG, "Failed fetching metadata from url %s", md_url)
76+ util.logexc(LOG, "Failed fetching %s from url %s", tree, md_url)
77 return {}
78
79+
80+def get_instance_metadata(api_version='latest',
81+ metadata_address='http://169.254.169.254',
82+ ssl_details=None, timeout=5, retries=5,
83+ leaf_decoder=None):
84+ # Note, 'meta-data' explicitly has trailing /.
85+ # this is required for CloudStack (LP: #1356855)
86+ return _get_instance_metadata(tree='meta-data/', api_version=api_version,
87+ metadata_address=metadata_address,
88+ ssl_details=ssl_details, timeout=timeout,
89+ retries=retries, leaf_decoder=leaf_decoder)
90+
91+
92+def get_instance_identity(api_version='latest',
93+ metadata_address='http://169.254.169.254',
94+ ssl_details=None, timeout=5, retries=5,
95+ leaf_decoder=None):
96+ return _get_instance_metadata(tree='dynamic/instance-identity',
97+ api_version=api_version,
98+ metadata_address=metadata_address,
99+ ssl_details=ssl_details, timeout=timeout,
100+ retries=retries, leaf_decoder=leaf_decoder)
101 # vi: ts=4 expandtab
102diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
103index 7bbbfb6..fd4b73b 100644
104--- a/cloudinit/sources/DataSourceEc2.py
105+++ b/cloudinit/sources/DataSourceEc2.py
106@@ -148,7 +148,12 @@ class DataSourceEc2(sources.DataSource):
107 return self.min_metadata_version
108
109 def get_instance_id(self):
110- return self.metadata['instance-id']
111+ if self.cloud_platform == Platforms.AWS:
112+ # Prefer the ID from the instance identity document, but fall back
113+ return self.identity.get(
114+ 'instanceId', self.metadata['instance-id'])
115+ else:
116+ return self.metadata['instance-id']
117
118 def _get_url_settings(self):
119 mcfg = self.ds_cfg
120@@ -262,15 +267,27 @@ class DataSourceEc2(sources.DataSource):
121 @property
122 def availability_zone(self):
123 try:
124- return self.metadata['placement']['availability-zone']
125+ if self.cloud_platform == Platforms.AWS:
126+ return self.identity.get(
127+ 'availabilityZone',
128+ self.metadata['placement']['availability-zone'])
129+ else:
130+ return self.metadata['placement']['availability-zone']
131 except KeyError:
132 return None
133
134 @property
135 def region(self):
136- az = self.availability_zone
137- if az is not None:
138- return az[:-1]
139+ if self.cloud_platform == Platforms.AWS:
140+ region = self.identity.get('region')
141+ # Fallback to trimming the availability zone if region is missing
142+ if self.availability_zone and not region:
143+ region = self.availability_zone[:-1]
144+ return region
145+ else:
146+ az = self.availability_zone
147+ if az is not None:
148+ return az[:-1]
149 return None
150
151 @property
152@@ -351,6 +368,9 @@ class DataSourceEc2(sources.DataSource):
153 api_version, self.metadata_address)
154 self.metadata = ec2.get_instance_metadata(
155 api_version, self.metadata_address)
156+ if self.cloud_platform == Platforms.AWS:
157+ self.identity = ec2.get_instance_identity(
158+ api_version, self.metadata_address).get('document', {})
159 except Exception:
160 util.logexc(
161 LOG, "Failed reading from metadata address %s",
162diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
163index 82ee971..dc548eb 100644
164--- a/tests/unittests/test_datasource/test_aliyun.py
165+++ b/tests/unittests/test_datasource/test_aliyun.py
166@@ -47,6 +47,9 @@ def register_mock_metaserver(base_url, data):
167 elif isinstance(body, list):
168 register(base_url.rstrip('/'), '\n'.join(body) + '\n')
169 elif isinstance(body, dict):
170+ if not body:
171+ register(base_url.rstrip('/') + '/', 'not found',
172+ status_code=404)
173 vals = []
174 for k, v in body.items():
175 if isinstance(v, (str, list)):
176@@ -91,9 +94,22 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
177 self.metadata_address,
178 self.ds.min_metadata_version, 'user-data')
179
180+ # EC2 provides an instance-identity document which must return 404 here
181+ # for this test to pass.
182+ @property
183+ def default_identity(self):
184+ return {}
185+
186+ @property
187+ def identity_url(self):
188+ return os.path.join(self.metadata_address,
189+ self.ds.min_metadata_version,
190+ 'dynamic', 'instance-identity')
191+
192 def regist_default_server(self):
193 register_mock_metaserver(self.metadata_url, self.default_metadata)
194 register_mock_metaserver(self.userdata_url, self.default_userdata)
195+ register_mock_metaserver(self.identity_url, self.default_identity)
196
197 def _test_get_data(self):
198 self.assertEqual(self.ds.metadata, self.default_metadata)

Subscribers

People subscribed via source and target branches