1064ba
From aeaaf3f3b3f426a7906a2e03d924b2e42528c600 Mon Sep 17 00:00:00 2001
bfa931
From: Scott Moser <smoser@brickies.net>
bfa931
Date: Thu, 23 Feb 2017 17:15:27 -0500
bfa931
Subject: [PATCH 2/5] DatasourceEc2: add warning message when not on AWS.
bfa931
bfa931
Based on the setting Datasource/Ec2/strict_id, the datasource
bfa931
will now warn once per instance.
bfa931
bfa931
(cherry picked from commit 9bb55c6c45bcc5e310cf7e4d42cad53759dcca15)
bfa931
1064ba
Resolves: rhbz#1482547
bfa931
bfa931
Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
bfa931
---
bfa931
 cloudinit/sources/DataSourceAliYun.py |   4 +
bfa931
 cloudinit/sources/DataSourceEc2.py    | 178 +++++++++++++++++++++++++++++++++-
bfa931
 2 files changed, 180 insertions(+), 2 deletions(-)
bfa931
bfa931
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
bfa931
index 2d00255c..9debe947 100644
bfa931
--- a/cloudinit/sources/DataSourceAliYun.py
bfa931
+++ b/cloudinit/sources/DataSourceAliYun.py
bfa931
@@ -22,6 +22,10 @@ class DataSourceAliYun(EC2.DataSourceEc2):
bfa931
     def get_public_ssh_keys(self):
bfa931
         return parse_public_keys(self.metadata.get('public-keys', {}))
bfa931
 
bfa931
+    @property
bfa931
+    def cloud_platform(self):
bfa931
+        return EC2.Platforms.ALIYUN
bfa931
+
bfa931
 
bfa931
 def parse_public_keys(public_keys):
bfa931
     keys = []
bfa931
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
bfa931
index c657fd09..26da263a 100644
bfa931
--- a/cloudinit/sources/DataSourceEc2.py
bfa931
+++ b/cloudinit/sources/DataSourceEc2.py
bfa931
@@ -9,6 +9,7 @@
bfa931
 # This file is part of cloud-init. See LICENSE file for license information.
bfa931
 
bfa931
 import os
bfa931
+import textwrap
bfa931
 import time
bfa931
 
bfa931
 from cloudinit import ec2_utils as ec2
bfa931
@@ -22,12 +23,23 @@ LOG = logging.getLogger(__name__)
bfa931
 # Which version we are requesting of the ec2 metadata apis
bfa931
 DEF_MD_VERSION = '2009-04-04'
bfa931
 
bfa931
+STRICT_ID_PATH = ("datasource", "Ec2", "strict_id")
bfa931
+STRICT_ID_DEFAULT = "warn"
bfa931
+
bfa931
+
bfa931
+class Platforms(object):
bfa931
+    ALIYUN = "AliYun"
bfa931
+    AWS = "AWS"
bfa931
+    SEEDED = "Seeded"
bfa931
+    UNKNOWN = "Unknown"
bfa931
+
bfa931
 
bfa931
 class DataSourceEc2(sources.DataSource):
bfa931
     # Default metadata urls that will be used if none are provided
bfa931
     # They will be checked for 'resolveability' and some of the
bfa931
     # following may be discarded if they do not resolve
bfa931
     metadata_urls = ["http://169.254.169.254", "http://instance-data.:8773"]
bfa931
+    _cloud_platform = None
bfa931
 
bfa931
     def __init__(self, sys_cfg, distro, paths):
bfa931
         sources.DataSource.__init__(self, sys_cfg, distro, paths)
bfa931
@@ -41,8 +53,18 @@ class DataSourceEc2(sources.DataSource):
bfa931
             self.userdata_raw = seed_ret['user-data']
bfa931
             self.metadata = seed_ret['meta-data']
bfa931
             LOG.debug("Using seeded ec2 data from %s", self.seed_dir)
bfa931
+            self._cloud_platform = Platforms.SEEDED
bfa931
             return True
bfa931
 
bfa931
+        strict_mode, _sleep = read_strict_mode(
bfa931
+            util.get_cfg_by_path(self.sys_cfg, STRICT_ID_PATH,
bfa931
+                                 STRICT_ID_DEFAULT), ("warn", None))
bfa931
+
bfa931
+        LOG.debug("strict_mode: %s, cloud_platform=%s",
bfa931
+                  strict_mode, self.cloud_platform)
bfa931
+        if strict_mode == "true" and self.cloud_platform == Platforms.UNKNOWN:
bfa931
+            return False
bfa931
+
bfa931
         try:
bfa931
             if not self.wait_for_metadata_service():
bfa931
                 return False
bfa931
@@ -51,8 +73,8 @@ class DataSourceEc2(sources.DataSource):
bfa931
                 ec2.get_instance_userdata(self.api_ver, self.metadata_address)
bfa931
             self.metadata = ec2.get_instance_metadata(self.api_ver,
bfa931
                                                       self.metadata_address)
bfa931
-            LOG.debug("Crawl of metadata service took %s seconds",
bfa931
-                      int(time.time() - start_time))
bfa931
+            LOG.debug("Crawl of metadata service took %.3f seconds",
bfa931
+                      time.time() - start_time)
bfa931
             return True
bfa931
         except Exception:
bfa931
             util.logexc(LOG, "Failed reading from metadata address %s",
bfa931
@@ -190,6 +212,158 @@ class DataSourceEc2(sources.DataSource):
bfa931
             return az[:-1]
bfa931
         return None
bfa931
 
bfa931
+    @property
bfa931
+    def cloud_platform(self):
bfa931
+        if self._cloud_platform is None:
bfa931
+            self._cloud_platform = identify_platform()
bfa931
+        return self._cloud_platform
bfa931
+
bfa931
+    def activate(self, cfg, is_new_instance):
bfa931
+        if not is_new_instance:
bfa931
+            return
bfa931
+        if self.cloud_platform == Platforms.UNKNOWN:
bfa931
+            warn_if_necessary(
bfa931
+                util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT))
bfa931
+
bfa931
+
bfa931
+def read_strict_mode(cfgval, default):
bfa931
+    try:
bfa931
+        return parse_strict_mode(cfgval)
bfa931
+    except ValueError as e:
bfa931
+        LOG.warn(e)
bfa931
+        return default
bfa931
+
bfa931
+
bfa931
+def parse_strict_mode(cfgval):
bfa931
+    # given a mode like:
bfa931
+    #    true, false, warn,[sleep]
bfa931
+    # return tuple with string mode (true|false|warn) and sleep.
bfa931
+    if cfgval is True:
bfa931
+        return 'true', None
bfa931
+    if cfgval is False:
bfa931
+        return 'false', None
bfa931
+
bfa931
+    if not cfgval:
bfa931
+        return 'warn', 0
bfa931
+
bfa931
+    mode, _, sleep = cfgval.partition(",")
bfa931
+    if mode not in ('true', 'false', 'warn'):
bfa931
+        raise ValueError(
bfa931
+            "Invalid mode '%s' in strict_id setting '%s': "
bfa931
+            "Expected one of 'true', 'false', 'warn'." % (mode, cfgval))
bfa931
+
bfa931
+    if sleep:
bfa931
+        try:
bfa931
+            sleep = int(sleep)
bfa931
+        except ValueError:
bfa931
+            raise ValueError("Invalid sleep '%s' in strict_id setting '%s': "
bfa931
+                             "not an integer" % (sleep, cfgval))
bfa931
+    else:
bfa931
+        sleep = None
bfa931
+
bfa931
+    return mode, sleep
bfa931
+
bfa931
+
bfa931
+def warn_if_necessary(cfgval):
bfa931
+    try:
bfa931
+        mode, sleep = parse_strict_mode(cfgval)
bfa931
+    except ValueError as e:
bfa931
+        LOG.warn(e)
bfa931
+        return
bfa931
+
bfa931
+    if mode == "false":
bfa931
+        return
bfa931
+
bfa931
+    show_warning(sleep)
bfa931
+
bfa931
+
bfa931
+def show_warning(sleep):
bfa931
+    message = textwrap.dedent("""
bfa931
+        ****************************************************************
bfa931
+        # This system is using the EC2 Metadata Service, but does not  #
bfa931
+        # appear to be running on Amazon EC2 or one of cloud-init's    #
bfa931
+        # known platforms that provide a EC2 Metadata service. In the  #
bfa931
+        # future, cloud-init may stop reading metadata from the EC2    #
bfa931
+        # Metadata Service unless the platform can be identified       #
bfa931
+        #                                                              #
bfa931
+        # If you are seeing this message, please file a bug against    #
bfa931
+        # cloud-init at https://bugs.launchpad.net/cloud-init/+filebug #
bfa931
+        # Make sure to include the cloud provider your instance is     #
bfa931
+        # running on.                                                  #
bfa931
+        #                                                              #
bfa931
+        # For more information see                                     #
bfa931
+        #   https://bugs.launchpad.net/cloud-init/+bug/1660385         #
bfa931
+        #                                                              #
bfa931
+        # After you have filed a bug, you can disable this warning by  #
bfa931
+        # launching your instance with the cloud-config below, or      #
bfa931
+        # putting that content into                                    #
bfa931
+        #    /etc/cloud/cloud.cfg.d/99-ec2-datasource.cfg              #
bfa931
+        #                                                              #
bfa931
+        # #cloud-config                                                #
bfa931
+        # datasource:                                                  #
bfa931
+        #  Ec2:                                                        #
bfa931
+        #   strict_id: false                                           #
bfa931
+        #                                                              #
bfa931
+        """)
bfa931
+    closemsg = ""
bfa931
+    if sleep:
bfa931
+        closemsg = "  [sleeping for %d seconds]  " % sleep
bfa931
+    message += closemsg.center(64, "*")
bfa931
+    print(message)
bfa931
+    LOG.warn(message)
bfa931
+    if sleep:
bfa931
+        time.sleep(sleep)
bfa931
+
bfa931
+
bfa931
+def identify_aws(data):
bfa931
+    # data is a dictionary returned by _collect_platform_data.
bfa931
+    if (data['uuid'].startswith('ec2') and
bfa931
+            (data['uuid_source'] == 'hypervisor' or
bfa931
+             data['uuid'] == data['serial'])):
bfa931
+            return Platforms.AWS
bfa931
+
bfa931
+    return None
bfa931
+
bfa931
+
bfa931
+def identify_platform():
bfa931
+    # identify the platform and return an entry in Platforms.
bfa931
+    data = _collect_platform_data()
bfa931
+    checks = (identify_aws, lambda x: Platforms.UNKNOWN)
bfa931
+    for checker in checks:
bfa931
+        try:
bfa931
+            result = checker(data)
bfa931
+            if result:
bfa931
+                return result
bfa931
+        except Exception as e:
bfa931
+            LOG.warn("calling %s with %s raised exception: %s",
bfa931
+                     checker, data, e)
bfa931
+
bfa931
+
bfa931
+def _collect_platform_data():
bfa931
+    # returns a dictionary with all lower case values:
bfa931
+    #   uuid: system-uuid from dmi or /sys/hypervisor
bfa931
+    #   uuid_source: 'hypervisor' (/sys/hypervisor/uuid) or 'dmi'
bfa931
+    #   serial: dmi 'system-serial-number' (/sys/.../product_serial)
bfa931
+    data = {}
bfa931
+    try:
bfa931
+        uuid = util.load_file("/sys/hypervisor/uuid").strip()
bfa931
+        data['uuid_source'] = 'hypervisor'
bfa931
+    except Exception:
bfa931
+        uuid = util.read_dmi_data('system-uuid')
bfa931
+        data['uuid_source'] = 'dmi'
bfa931
+
bfa931
+    if uuid is None:
bfa931
+        uuid = ''
bfa931
+    data['uuid'] = uuid.lower()
bfa931
+
bfa931
+    serial = util.read_dmi_data('system-serial-number')
bfa931
+    if serial is None:
bfa931
+        serial = ''
bfa931
+
bfa931
+    data['serial'] = serial.lower()
bfa931
+
bfa931
+    return data
bfa931
+
bfa931
 
bfa931
 # Used to match classes to dependencies
bfa931
 datasources = [
bfa931
-- 
bfa931
2.13.5
bfa931