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
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index d5becd1..e576c24 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -45,6 +45,10 @@ OSFAMILIES = {
4545
46LOG = logging.getLogger(__name__)46LOG = logging.getLogger(__name__)
4747
48# This is a best guess regex, based on current EC2 AZs, it could break when
49# Amazon adds new regions and new AZs.
50_EC2_AZ_RE = re.compile('^[a-z][a-z]-(?:[a-z]+-)+[0-9][a-z]$')
51
4852
49@six.add_metaclass(abc.ABCMeta)53@six.add_metaclass(abc.ABCMeta)
50class Distro(object):54class Distro(object):
@@ -686,18 +690,13 @@ def _get_package_mirror_info(mirror_info, data_source=None,
686 if not mirror_info:690 if not mirror_info:
687 mirror_info = {}691 mirror_info = {}
688692
689 # ec2 availability zones are named cc-direction-[0-9][a-d] (us-east-1b)
690 # the region is us-east-1. so region = az[0:-1]
691 directions_re = '|'.join([
692 'central', 'east', 'north', 'northeast', 'northwest',
693 'south', 'southeast', 'southwest', 'west'])
694 ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" % directions_re)
695
696 subst = {}693 subst = {}
697 if data_source and data_source.availability_zone:694 if data_source and data_source.availability_zone:
698 subst['availability_zone'] = data_source.availability_zone695 subst['availability_zone'] = data_source.availability_zone
699696
700 if re.match(ec2_az_re, data_source.availability_zone):697 # ec2 availability zones are named cc-direction-[0-9][a-d] (us-east-1b)
698 # the region is us-east-1. so region = az[0:-1]
699 if _EC2_AZ_RE.match(data_source.availability_zone):
701 subst['ec2_region'] = "%s" % data_source.availability_zone[0:-1]700 subst['ec2_region'] = "%s" % data_source.availability_zone[0:-1]
702701
703 if data_source and data_source.region:702 if data_source and data_source.region:
diff --git a/cloudinit/ec2_utils.py b/cloudinit/ec2_utils.py
index 723d6bd..d6c61e4 100644
--- a/cloudinit/ec2_utils.py
+++ b/cloudinit/ec2_utils.py
@@ -1,6 +1,8 @@
1# Copyright (C) 2012 Yahoo! Inc.1# Copyright (C) 2012 Yahoo! Inc.
2# Copyright (C) 2014 Amazon.com, Inc. or its affiliates.
2#3#
3# Author: Joshua Harlow <harlowja@yahoo-inc.com>4# Author: Joshua Harlow <harlowja@yahoo-inc.com>
5# Author: Andrew Jorgensen <ajorgens@amazon.com>
4#6#
5# This file is part of cloud-init. See LICENSE file for license information.7# This file is part of cloud-init. See LICENSE file for license information.
68
@@ -164,14 +166,11 @@ def get_instance_userdata(api_version='latest',
164 return user_data166 return user_data
165167
166168
167def get_instance_metadata(api_version='latest',169def _get_instance_metadata(tree, api_version='latest',
168 metadata_address='http://169.254.169.254',170 metadata_address='http://169.254.169.254',
169 ssl_details=None, timeout=5, retries=5,171 ssl_details=None, timeout=5, retries=5,
170 leaf_decoder=None):172 leaf_decoder=None):
171 md_url = url_helper.combine_url(metadata_address, api_version)173 md_url = url_helper.combine_url(metadata_address, api_version, tree)
172 # Note, 'meta-data' explicitly has trailing /.
173 # this is required for CloudStack (LP: #1356855)
174 md_url = url_helper.combine_url(md_url, 'meta-data/')
175 caller = functools.partial(util.read_file_or_url,174 caller = functools.partial(util.read_file_or_url,
176 ssl_details=ssl_details, timeout=timeout,175 ssl_details=ssl_details, timeout=timeout,
177 retries=retries)176 retries=retries)
@@ -189,7 +188,29 @@ def get_instance_metadata(api_version='latest',
189 md = {}188 md = {}
190 return md189 return md
191 except Exception:190 except Exception:
192 util.logexc(LOG, "Failed fetching metadata from url %s", md_url)191 util.logexc(LOG, "Failed fetching %s from url %s", tree, md_url)
193 return {}192 return {}
194193
194
195def get_instance_metadata(api_version='latest',
196 metadata_address='http://169.254.169.254',
197 ssl_details=None, timeout=5, retries=5,
198 leaf_decoder=None):
199 # Note, 'meta-data' explicitly has trailing /.
200 # this is required for CloudStack (LP: #1356855)
201 return _get_instance_metadata(tree='meta-data/', api_version=api_version,
202 metadata_address=metadata_address,
203 ssl_details=ssl_details, timeout=timeout,
204 retries=retries, leaf_decoder=leaf_decoder)
205
206
207def get_instance_identity(api_version='latest',
208 metadata_address='http://169.254.169.254',
209 ssl_details=None, timeout=5, retries=5,
210 leaf_decoder=None):
211 return _get_instance_metadata(tree='dynamic/instance-identity',
212 api_version=api_version,
213 metadata_address=metadata_address,
214 ssl_details=ssl_details, timeout=timeout,
215 retries=retries, leaf_decoder=leaf_decoder)
195# vi: ts=4 expandtab216# vi: ts=4 expandtab
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 7bbbfb6..fd4b73b 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -148,7 +148,12 @@ class DataSourceEc2(sources.DataSource):
148 return self.min_metadata_version148 return self.min_metadata_version
149149
150 def get_instance_id(self):150 def get_instance_id(self):
151 return self.metadata['instance-id']151 if self.cloud_platform == Platforms.AWS:
152 # Prefer the ID from the instance identity document, but fall back
153 return self.identity.get(
154 'instanceId', self.metadata['instance-id'])
155 else:
156 return self.metadata['instance-id']
152157
153 def _get_url_settings(self):158 def _get_url_settings(self):
154 mcfg = self.ds_cfg159 mcfg = self.ds_cfg
@@ -262,15 +267,27 @@ class DataSourceEc2(sources.DataSource):
262 @property267 @property
263 def availability_zone(self):268 def availability_zone(self):
264 try:269 try:
265 return self.metadata['placement']['availability-zone']270 if self.cloud_platform == Platforms.AWS:
271 return self.identity.get(
272 'availabilityZone',
273 self.metadata['placement']['availability-zone'])
274 else:
275 return self.metadata['placement']['availability-zone']
266 except KeyError:276 except KeyError:
267 return None277 return None
268278
269 @property279 @property
270 def region(self):280 def region(self):
271 az = self.availability_zone281 if self.cloud_platform == Platforms.AWS:
272 if az is not None:282 region = self.identity.get('region')
273 return az[:-1]283 # Fallback to trimming the availability zone if region is missing
284 if self.availability_zone and not region:
285 region = self.availability_zone[:-1]
286 return region
287 else:
288 az = self.availability_zone
289 if az is not None:
290 return az[:-1]
274 return None291 return None
275292
276 @property293 @property
@@ -351,6 +368,9 @@ class DataSourceEc2(sources.DataSource):
351 api_version, self.metadata_address)368 api_version, self.metadata_address)
352 self.metadata = ec2.get_instance_metadata(369 self.metadata = ec2.get_instance_metadata(
353 api_version, self.metadata_address)370 api_version, self.metadata_address)
371 if self.cloud_platform == Platforms.AWS:
372 self.identity = ec2.get_instance_identity(
373 api_version, self.metadata_address).get('document', {})
354 except Exception:374 except Exception:
355 util.logexc(375 util.logexc(
356 LOG, "Failed reading from metadata address %s",376 LOG, "Failed reading from metadata address %s",
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index 82ee971..dc548eb 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -47,6 +47,9 @@ def register_mock_metaserver(base_url, data):
47 elif isinstance(body, list):47 elif isinstance(body, list):
48 register(base_url.rstrip('/'), '\n'.join(body) + '\n')48 register(base_url.rstrip('/'), '\n'.join(body) + '\n')
49 elif isinstance(body, dict):49 elif isinstance(body, dict):
50 if not body:
51 register(base_url.rstrip('/') + '/', 'not found',
52 status_code=404)
50 vals = []53 vals = []
51 for k, v in body.items():54 for k, v in body.items():
52 if isinstance(v, (str, list)):55 if isinstance(v, (str, list)):
@@ -91,9 +94,22 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
91 self.metadata_address,94 self.metadata_address,
92 self.ds.min_metadata_version, 'user-data')95 self.ds.min_metadata_version, 'user-data')
9396
97 # EC2 provides an instance-identity document which must return 404 here
98 # for this test to pass.
99 @property
100 def default_identity(self):
101 return {}
102
103 @property
104 def identity_url(self):
105 return os.path.join(self.metadata_address,
106 self.ds.min_metadata_version,
107 'dynamic', 'instance-identity')
108
94 def regist_default_server(self):109 def regist_default_server(self):
95 register_mock_metaserver(self.metadata_url, self.default_metadata)110 register_mock_metaserver(self.metadata_url, self.default_metadata)
96 register_mock_metaserver(self.userdata_url, self.default_userdata)111 register_mock_metaserver(self.userdata_url, self.default_userdata)
112 register_mock_metaserver(self.identity_url, self.default_identity)
97113
98 def _test_get_data(self):114 def _test_get_data(self):
99 self.assertEqual(self.ds.metadata, self.default_metadata)115 self.assertEqual(self.ds.metadata, self.default_metadata)

Subscribers

People subscribed via source and target branches