cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #01581
[Merge] ~smoser/cloud-init:feature/ec2-ds-warn into cloud-init:master
Scott Moser has proposed merging ~smoser/cloud-init:feature/ec2-ds-warn into cloud-init:master.
Requested reviews:
cloud init development team (cloud-init-dev)
Related bugs:
Bug #1660385 in cloud-init: "Alert user of Ec2 Datasource on lookalike cloud"
https://bugs.launchpad.net/cloud-init/+bug/1660385
For more details, see:
https://code.launchpad.net/~smoser/cloud-init/+git/cloud-init/+merge/316033
--
Your team cloud init development team is requested to review the proposed merge of ~smoser/cloud-init:feature/ec2-ds-warn into cloud-init:master.
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
index 2d00255..9debe94 100644
--- a/cloudinit/sources/DataSourceAliYun.py
+++ b/cloudinit/sources/DataSourceAliYun.py
@@ -22,6 +22,10 @@ class DataSourceAliYun(EC2.DataSourceEc2):
def get_public_ssh_keys(self):
return parse_public_keys(self.metadata.get('public-keys', {}))
+ @property
+ def cloud_platform(self):
+ return EC2.Platforms.ALIYUN
+
def parse_public_keys(public_keys):
keys = []
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index c657fd0..cbcb61c 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -22,6 +22,69 @@ LOG = logging.getLogger(__name__)
# Which version we are requesting of the ec2 metadata apis
DEF_MD_VERSION = '2009-04-04'
+LOOKALIKE_BEHAVIOR = 'skip'
+LOOKALIKE_WARNING = """\
+************************************************************************
+This system is using the EC2 Metadata Service, but does not appear to be
+running on Amazon EC2. At a date yet to be determined, cloud-init will
+stop reading metadata from the EC2 Metadata service unless the platform
+can be identified.
+
+If you are seeing this message, please file a bug against cloud-init
+at https://bugs.launchpad.net/cloud-init/+filebug . Make sure to
+include the cloud provider your instance is running on.
+
+After you have filed a bug, you can disable this warning by launching an
+instance with the cloud-config below, or putting that content into
+/etc/cloud/cloud.cfg.d/99-ec2-lookalike.cfg.
+
+ #cloud-config
+ datasource:
+ Ec2:
+ look_alike:
+ behavior: accept
+
+************************************************************************
+"""
+
+
+class _PoorMansEnum(object):
+ """A thing with enum like behavior, here to avoid dependency on enum
+ which is not present in python 2.x standard library."""
+ def __init__(self, **kwargs):
+ for (k, v) in kwargs.items():
+ setattr(self, k, v)
+ self._keys = tuple(sorted([k for k in kwargs.keys()]))
+ self._values = tuple([kwargs[k] for k in self._keys])
+
+ def __contains__(self, k):
+ return k in self._values
+
+ def keys(self):
+ return self._keys
+
+ def values(self):
+ return self._values
+
+ def dict(self):
+ return dict(zip(self.keys(), self.values()))
+
+ def __str__(self):
+ return str(self.dict())
+
+
+Platforms = _PoorMansEnum(
+ ALIYUN="AliYun",
+ GENUINE_AWS="GenuineAWS",
+ UNKNOWN="Unknown",
+)
+
+LOOKALIKE_DEFAULT = {
+ 'behavior': 'skip',
+ 'platform': Platforms.UNKNOWN,
+ 'sleep': 10,
+}
+
class DataSourceEc2(sources.DataSource):
# Default metadata urls that will be used if none are provided
@@ -34,8 +97,23 @@ class DataSourceEc2(sources.DataSource):
self.metadata_address = None
self.seed_dir = os.path.join(paths.seed_dir, "ec2")
self.api_ver = DEF_MD_VERSION
+ self._cloud_platform = None
def get_data(self):
+ if self.cloud_platform == Platforms.UNKNOWN:
+ lcfg = lookalike_cfg(self.ds_cfg)
+ msg = ("Did not identify host as a EC2 Metadata Platform. "
+ "behavior = %s: %s")
+ behavior = lcfg['behavior']
+ if behavior == "skip":
+ LOG.debug(msg, behavior, "Not fetching EC2 Metadata")
+ return
+ else:
+ LOG.debug(msg, behavior, "Will attempt to fetch metadata.")
+ else:
+ LOG.info("Identified EC2 Metadata Platform: %s",
+ self.cloud_platform)
+
seed_ret = {}
if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
self.userdata_raw = seed_ret['user-data']
@@ -190,6 +268,117 @@ class DataSourceEc2(sources.DataSource):
return az[:-1]
return None
+ @property
+ def cloud_platform(self):
+ if self._cloud_platform is None:
+ platform = lookalike_cfg(self.ds_cfg)['platform']
+ if platform is Platforms.UNKNOWN:
+ self._cloud_platform = identify_platform()
+ else:
+ self._cloud_platform = platform
+ return self._cloud_platform
+
+ def activate(self, cfg, is_new_instance):
+ if is_new_instance:
+ lbehave, lsleep = lookalike_cfg(cfg)
+ apply_lookalike_settings(lbehave, lsleep)
+ return
+
+
+def lookalike_cfg(cfg):
+ # return a copy of the default lookalike config updated with
+ # values from cfg['look_alike']
+ result = LOOKALIKE_DEFAULT.copy()
+ if cfg is None:
+ return result
+ if not isinstance(cfg, dict):
+ raise ValueError("lookalike_cfg expected dict, found: %s", cfg)
+
+ lcfg = cfg.get('look_alike')
+ if lcfg is None:
+ return result
+ if not isinstance(cfg, dict):
+ raise ValueError("'look_alike' in input was not a dict: %s" % lcfg)
+
+ msg = 'Bad value {0} for look_alike/{1}. Using default: {2}'
+
+ def check_or_set_default(key, val, allowed):
+ if val in allowed:
+ result[key] = val
+ else:
+ LOG.warn(msg.format(val, key, result[key]))
+
+ check_or_set_default('behavior', lcfg.get('behavior'),
+ ('warn', 'skip', 'accept'))
+ check_or_set_default('platform', lcfg.get('platform'), Platforms)
+
+ if 'sleep' in lcfg:
+ sval = cfg.get('sleep')
+ try:
+ result['sleep'] = int(sval, result['sleep'])
+ except:
+ LOG.warn(msg.format(sval, 'sleep', result['sleep']))
+
+ return result['behavior'], result['sleep'], result['platform']
+
+
+def apply_lookalike_settings(behavior, sleep):
+ if behavior == 'warn':
+ LOG.warn(LOOKALIKE_WARNING)
+ if sleep:
+ time.sleep(sleep)
+ return
+
+
+def identify_aws(data):
+ # data is a dictionary returned by _collect_platform_data.
+ if (data['uuid'].startswith('ec2') and
+ (data['uuid_source'] == 'hypervisor' or
+ data['uuid'] == data['serial'])):
+ return Platforms.GENUINE_AWS
+
+ return None
+
+
+def identify_platform():
+ # identify the platform and return an entry in Platforms.
+ data = _collect_platform_data()
+ checks = (identify_aws, lambda x: Platforms.UNKNOWN)
+ for checker in checks:
+ try:
+ result = checker(data)
+ if result:
+ return result
+ except Exception as e:
+ LOG.warn("calling %s with %s raised exception: %s",
+ checker, data, e)
+
+
+def _collect_platform_data():
+ # returns a dictionary with all lower case values:
+ # uuid: system-uuid from dmi or /sys/hypervisor
+ # uuid_source: 'hypervisor' (/sys/hypervisor/uuid) or 'dmi'
+ # serial: dmi 'system-serial-number'
+ data = {}
+ try:
+ uuid = util.load_file("/sys/hypervisor/uuid").strip()
+ data['uuid_source'] = 'hypervisor'
+ except:
+ uuid = util.read_dmi_data('system-uuid')
+ data['uuid_source'] = 'dmi'
+
+ if uuid is None:
+ uuid = ''
+ data['uuid'] = uuid.lower()
+
+ serial = util.read_dmi_data('system-serial-number')
+ if serial is None:
+ serial = ''
+
+ data['serial'] = serial.lower()
+
+ return data
+
# Used to match classes to dependencies
datasources = [
diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst
index 4810c98..b8f4119 100644
--- a/doc/rtd/topics/datasources/ec2.rst
+++ b/doc/rtd/topics/datasources/ec2.rst
@@ -8,6 +8,8 @@ provided to the instance by the cloud provider. Typically this ip is
instance so that the instance can make calls to get instance userdata and
instance metadata.
+It supports reading from the `EC2 Instance Metadata`_ service.
+
Metadata is accessible via the following URL:
::
@@ -58,4 +60,34 @@ To see which versions are supported from your cloud provider use the following U
...
latest
+Configuration
+-------------
+Configuration for the datasource can be done from system config.
+The default values are shown as example below.
+
+.. code:: yaml
+
+ datasource:
+ Ec2:
+ # timeout: the timeout value for a request to the metadata service
+ timeout : 50
+ # The length in seconds to wait before giving up on the metadata
+ # service. The actual total wait could be up to
+ # len(resolvable_metadata_urls)*timeout
+ max_wait : 120
+ metadata_urls:
+ - http://169.254.169.254:80
+ - http://instance-data:8773
+
+ # 'look_alike' settings control what behavior is taken if this datasource
+ # is used but the system does not appear to be on EC2. See bug 1660385
+ # for more information: http://bugs.launchpad.net/bugs/1660385
+ # behavior can be 'warn', 'accept', 'skip'
+ # sleep: during first instance boot, sleep N seconds.
+ look_alike:
+ - behavior: warn
+ - sleep: 10
+
+
+.. _EC2 Instance Metadata: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
.. vi: textwidth=78
Follow ups