From 8ddb57149281ba2658696f19c1eb96e7769381e4 Mon Sep 17 00:00:00 2001 From: Sankar Tanguturi Date: Tue, 6 Sep 2016 14:51:32 -0700 Subject: Fixed Misc issues related to VMware customization. - staticIPV4 property can be either None or a valid Array. Need to check for None before accessing the ip address. - Modified few misc. log messages. - Added a new log message while waiting for the customization config file. - Added support to configure the maximum amount of time to wait for the customization config file. - VMware Customization Support is provided only for DataSourceOVF class and not for any other child classes. Implemented a new variable vmware_customization_supported to check whether the 'VMware Customization' support is available for a specific datasource or not. - Changed the function get_vmware_cust_settings to get_max_wait_from_cfg. - Removed the code that does 'ifdown and iup' in NIC configurator. --- cloudinit/sources/DataSourceOVF.py | 37 +++++++++++++++++++--- cloudinit/sources/helpers/vmware/imc/config_nic.py | 24 ++++---------- 2 files changed, 39 insertions(+), 22 deletions(-) (limited to 'cloudinit/sources') diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py index 78928c77..d70784ac 100644 --- a/cloudinit/sources/DataSourceOVF.py +++ b/cloudinit/sources/DataSourceOVF.py @@ -48,6 +48,7 @@ class DataSourceOVF(sources.DataSource): self.environment = None self.cfg = {} self.supported_seed_starts = ("/", "file://") + self.vmware_customization_supported = True def __str__(self): root = sources.DataSource.__str__(self) @@ -78,7 +79,10 @@ class DataSourceOVF(sources.DataSource): found.append(seed) elif system_type and 'vmware' in system_type.lower(): LOG.debug("VMware Virtualization Platform found") - if not util.get_cfg_option_bool( + if not self.vmware_customization_supported: + LOG.debug("Skipping the check for " + "VMware Customization support") + elif not util.get_cfg_option_bool( self.sys_cfg, "disable_vmware_customization", True): deployPkgPluginPath = search_file("/usr/lib/vmware-tools", "libdeployPkgPlugin.so") @@ -90,17 +94,18 @@ class DataSourceOVF(sources.DataSource): # copies the customization specification file to # /var/run/vmware-imc directory. cloud-init code needs # to search for the file in that directory. + max_wait = get_max_wait_from_cfg(self.ds_cfg) vmwareImcConfigFilePath = util.log_time( logfunc=LOG.debug, msg="waiting for configuration file", func=wait_for_imc_cfg_file, - args=("/var/run/vmware-imc", "cust.cfg")) + args=("/var/run/vmware-imc", "cust.cfg", max_wait)) if vmwareImcConfigFilePath: - LOG.debug("Found VMware DeployPkg Config File at %s" % + LOG.debug("Found VMware Customization Config File at %s", vmwareImcConfigFilePath) else: - LOG.debug("Did not find VMware DeployPkg Config File Path") + LOG.debug("Did not find VMware Customization Config File") else: LOG.debug("Customization for VMware platform is disabled.") @@ -206,6 +211,29 @@ class DataSourceOVFNet(DataSourceOVF): DataSourceOVF.__init__(self, sys_cfg, distro, paths) self.seed_dir = os.path.join(paths.seed_dir, 'ovf-net') self.supported_seed_starts = ("http://", "https://", "ftp://") + self.vmware_customization_supported = False + + +def get_max_wait_from_cfg(cfg): + default_max_wait = 90 + max_wait_cfg_option = 'vmware_cust_file_max_wait' + max_wait = default_max_wait + + if not cfg: + return max_wait + + try: + max_wait = int(cfg.get(max_wait_cfg_option, default_max_wait)) + except ValueError: + LOG.warn("Failed to get '%s', using %s", + max_wait_cfg_option, default_max_wait) + + if max_wait <= 0: + LOG.warn("Invalid value '%s' for '%s', using '%s' instead", + max_wait, max_wait_cfg_option, default_max_wait) + max_wait = default_max_wait + + return max_wait def wait_for_imc_cfg_file(dirpath, filename, maxwait=180, naplen=5): @@ -215,6 +243,7 @@ def wait_for_imc_cfg_file(dirpath, filename, maxwait=180, naplen=5): fileFullPath = search_file(dirpath, filename) if fileFullPath: return fileFullPath + LOG.debug("Waiting for VMware Customization Config File") time.sleep(naplen) waited += naplen return None diff --git a/cloudinit/sources/helpers/vmware/imc/config_nic.py b/cloudinit/sources/helpers/vmware/imc/config_nic.py index d5a7c346..67ac21db 100644 --- a/cloudinit/sources/helpers/vmware/imc/config_nic.py +++ b/cloudinit/sources/helpers/vmware/imc/config_nic.py @@ -101,7 +101,11 @@ class NicConfigurator(object): return lines # Static Ipv4 - v4 = nic.staticIpv4 + addrs = nic.staticIpv4 + if not addrs: + return lines + + v4 = addrs[0] if v4.ip: lines.append(' address %s' % v4.ip) if v4.netmask: @@ -197,22 +201,6 @@ class NicConfigurator(object): util.subp(["pkill", "dhclient"], rcs=[0, 1]) util.subp(["rm", "-f", "/var/lib/dhcp/*"]) - def if_down_up(self): - names = [] - for nic in self.nics: - name = self.mac2Name.get(nic.mac.lower()) - names.append(name) - - for name in names: - logger.info('Bring down interface %s' % name) - util.subp(["ifdown", "%s" % name]) - - self.clear_dhcp() - - for name in names: - logger.info('Bring up interface %s' % name) - util.subp(["ifup", "%s" % name]) - def configure(self): """ Configure the /etc/network/intefaces @@ -232,6 +220,6 @@ class NicConfigurator(object): for line in lines: fp.write('%s\n' % line) - self.if_down_up() + self.clear_dhcp() # vi: ts=4 expandtab -- cgit v1.2.3 From 4cf53f1544f8f5629330eab3efef1a18255c277a Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman Date: Tue, 17 Jan 2017 08:53:22 -0500 Subject: OpenStack: Use timeout and retries from config in get_data. This modifies get_data in DataSourceOpenStack.py to get the timeout and retries values from the data source configuration, rather than from keyword arguments. This permits get_data to use the same timeout as other methods, and allows an operator to increase the timeout in environments where the metadata service takes longer than five seconds to respond. LP: #1657130 Resolves: rhbz#1408589 --- cloudinit/sources/DataSourceOpenStack.py | 15 ++++++++++++--- tests/unittests/test_datasource/test_openstack.py | 8 ++++---- 2 files changed, 16 insertions(+), 7 deletions(-) (limited to 'cloudinit/sources') diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py index 2a58f1cd..e1ea21f8 100644 --- a/cloudinit/sources/DataSourceOpenStack.py +++ b/cloudinit/sources/DataSourceOpenStack.py @@ -45,6 +45,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource): # max_wait < 0 indicates do not wait max_wait = -1 timeout = 10 + retries = 5 try: max_wait = int(self.ds_cfg.get("max_wait", max_wait)) @@ -55,7 +56,13 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource): timeout = max(0, int(self.ds_cfg.get("timeout", timeout))) except Exception: util.logexc(LOG, "Failed to get timeout, using %s", timeout) - return (max_wait, timeout) + + try: + retries = int(self.ds_cfg.get("retries", retries)) + except Exception: + util.logexc(LOG, "Failed to get max wait. using %s", retries) + + return (max_wait, timeout, retries) def wait_for_metadata_service(self): urls = self.ds_cfg.get("metadata_urls", [DEF_MD_URL]) @@ -76,7 +83,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource): md_urls.append(md_url) url2base[md_url] = url - (max_wait, timeout) = self._get_url_settings() + (max_wait, timeout, retries) = self._get_url_settings() start_time = time.time() avail_url = url_helper.wait_for_url(urls=md_urls, max_wait=max_wait, timeout=timeout) @@ -89,13 +96,15 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource): self.metadata_address = url2base.get(avail_url) return bool(avail_url) - def get_data(self, retries=5, timeout=5): + def get_data(self): try: if not self.wait_for_metadata_service(): return False except IOError: return False + (max_wait, timeout, retries) = self._get_url_settings() + try: results = util.log_time(LOG.debug, 'Crawl of openstack metadata service', diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py index e5b6fcc6..28e1833e 100644 --- a/tests/unittests/test_datasource/test_openstack.py +++ b/tests/unittests/test_datasource/test_openstack.py @@ -232,7 +232,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): None, helpers.Paths({})) self.assertIsNone(ds_os.version) - found = ds_os.get_data(timeout=0.1, retries=0) + found = ds_os.get_data() self.assertTrue(found) self.assertEqual(2, ds_os.version) md = dict(ds_os.metadata) @@ -256,7 +256,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): None, helpers.Paths({})) self.assertIsNone(ds_os.version) - found = ds_os.get_data(timeout=0.1, retries=0) + found = ds_os.get_data() self.assertFalse(found) self.assertIsNone(ds_os.version) @@ -275,7 +275,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): 'timeout': 0, } self.assertIsNone(ds_os.version) - found = ds_os.get_data(timeout=0.1, retries=0) + found = ds_os.get_data() self.assertFalse(found) self.assertIsNone(ds_os.version) @@ -298,7 +298,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): 'timeout': 0, } self.assertIsNone(ds_os.version) - found = ds_os.get_data(timeout=0.1, retries=0) + found = ds_os.get_data() self.assertFalse(found) self.assertIsNone(ds_os.version) -- cgit v1.2.3 From 9bb55c6c45bcc5e310cf7e4d42cad53759dcca15 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Thu, 23 Feb 2017 17:15:27 -0500 Subject: DatasourceEc2: add warning message when not on AWS. Based on the setting Datasource/Ec2/strict_id, the datasource will now warn once per instance. --- cloudinit/sources/DataSourceAliYun.py | 4 + cloudinit/sources/DataSourceEc2.py | 178 +++++++++++++++++++++++++++++++++- tools/ds-identify | 40 ++++++-- 3 files changed, 211 insertions(+), 11 deletions(-) (limited to 'cloudinit/sources') diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py index 2d00255c..9debe947 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 c657fd09..26da263a 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -9,6 +9,7 @@ # This file is part of cloud-init. See LICENSE file for license information. import os +import textwrap import time from cloudinit import ec2_utils as ec2 @@ -22,12 +23,23 @@ LOG = logging.getLogger(__name__) # Which version we are requesting of the ec2 metadata apis DEF_MD_VERSION = '2009-04-04' +STRICT_ID_PATH = ("datasource", "Ec2", "strict_id") +STRICT_ID_DEFAULT = "warn" + + +class Platforms(object): + ALIYUN = "AliYun" + AWS = "AWS" + SEEDED = "Seeded" + UNKNOWN = "Unknown" + class DataSourceEc2(sources.DataSource): # Default metadata urls that will be used if none are provided # They will be checked for 'resolveability' and some of the # following may be discarded if they do not resolve metadata_urls = ["http://169.254.169.254", "http://instance-data.:8773"] + _cloud_platform = None def __init__(self, sys_cfg, distro, paths): sources.DataSource.__init__(self, sys_cfg, distro, paths) @@ -41,8 +53,18 @@ class DataSourceEc2(sources.DataSource): self.userdata_raw = seed_ret['user-data'] self.metadata = seed_ret['meta-data'] LOG.debug("Using seeded ec2 data from %s", self.seed_dir) + self._cloud_platform = Platforms.SEEDED return True + strict_mode, _sleep = read_strict_mode( + util.get_cfg_by_path(self.sys_cfg, STRICT_ID_PATH, + STRICT_ID_DEFAULT), ("warn", None)) + + LOG.debug("strict_mode: %s, cloud_platform=%s", + strict_mode, self.cloud_platform) + if strict_mode == "true" and self.cloud_platform == Platforms.UNKNOWN: + return False + try: if not self.wait_for_metadata_service(): return False @@ -51,8 +73,8 @@ class DataSourceEc2(sources.DataSource): ec2.get_instance_userdata(self.api_ver, self.metadata_address) self.metadata = ec2.get_instance_metadata(self.api_ver, self.metadata_address) - LOG.debug("Crawl of metadata service took %s seconds", - int(time.time() - start_time)) + LOG.debug("Crawl of metadata service took %.3f seconds", + time.time() - start_time) return True except Exception: util.logexc(LOG, "Failed reading from metadata address %s", @@ -190,6 +212,158 @@ class DataSourceEc2(sources.DataSource): return az[:-1] return None + @property + def cloud_platform(self): + if self._cloud_platform is None: + self._cloud_platform = identify_platform() + return self._cloud_platform + + def activate(self, cfg, is_new_instance): + if not is_new_instance: + return + if self.cloud_platform == Platforms.UNKNOWN: + warn_if_necessary( + util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT)) + + +def read_strict_mode(cfgval, default): + try: + return parse_strict_mode(cfgval) + except ValueError as e: + LOG.warn(e) + return default + + +def parse_strict_mode(cfgval): + # given a mode like: + # true, false, warn,[sleep] + # return tuple with string mode (true|false|warn) and sleep. + if cfgval is True: + return 'true', None + if cfgval is False: + return 'false', None + + if not cfgval: + return 'warn', 0 + + mode, _, sleep = cfgval.partition(",") + if mode not in ('true', 'false', 'warn'): + raise ValueError( + "Invalid mode '%s' in strict_id setting '%s': " + "Expected one of 'true', 'false', 'warn'." % (mode, cfgval)) + + if sleep: + try: + sleep = int(sleep) + except ValueError: + raise ValueError("Invalid sleep '%s' in strict_id setting '%s': " + "not an integer" % (sleep, cfgval)) + else: + sleep = None + + return mode, sleep + + +def warn_if_necessary(cfgval): + try: + mode, sleep = parse_strict_mode(cfgval) + except ValueError as e: + LOG.warn(e) + return + + if mode == "false": + return + + show_warning(sleep) + + +def show_warning(sleep): + message = textwrap.dedent(""" + **************************************************************** + # This system is using the EC2 Metadata Service, but does not # + # appear to be running on Amazon EC2 or one of cloud-init's # + # known platforms that provide a EC2 Metadata service. In the # + # future, cloud-init may 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. # + # # + # For more information see # + # https://bugs.launchpad.net/cloud-init/+bug/1660385 # + # # + # After you have filed a bug, you can disable this warning by # + # launching your instance with the cloud-config below, or # + # putting that content into # + # /etc/cloud/cloud.cfg.d/99-ec2-datasource.cfg # + # # + # #cloud-config # + # datasource: # + # Ec2: # + # strict_id: false # + # # + """) + closemsg = "" + if sleep: + closemsg = " [sleeping for %d seconds] " % sleep + message += closemsg.center(64, "*") + print(message) + LOG.warn(message) + if sleep: + time.sleep(sleep) + + +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.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' (/sys/.../product_serial) + data = {} + try: + uuid = util.load_file("/sys/hypervisor/uuid").strip() + data['uuid_source'] = 'hypervisor' + except Exception: + 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/tools/ds-identify b/tools/ds-identify index bfb55ed1..dfa856ff 100755 --- a/tools/ds-identify +++ b/tools/ds-identify @@ -635,28 +635,50 @@ ec2_read_strict_setting() { return 0 } -dscheck_Ec2() { - # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html - # http://paste.ubuntu.com/23630859/ - - check_seed_dir "ec2" meta-data user-data && return ${DS_FOUND} - is_container && return ${DS_NOT_FOUND} +ec2_identify_platform() { + local default="$1" + local serial="${DI_DMI_PRODUCT_SERIAL}" + # AWS http://docs.aws.amazon.com/AWSEC2/ + # latest/UserGuide/identify_ec2_instances.html local uuid="" hvuuid="$PATH_ROOT/sys/hypervisor/uuid" # if the (basically) xen specific /sys/hypervisor/uuid starts with 'ec2' if [ -r "$hvuuid" ] && read uuid < "$hvuuid" && [ "${uuid#ec2}" != "$uuid" ]; then - return ${DS_FOUND} + _RET="AWS" + return 0 fi # product uuid and product serial start with case insensitive - local uuid="${DI_DMI_PRODUCT_UUID}" serial="${DI_DMI_PRODUCT_SERIAL}" + local uuid="${DI_DMI_PRODUCT_UUID}" case "$uuid:$serial" in [Ee][Cc]2*:[Ee][Cc]2) # both start with ec2, now check for case insenstive equal - nocase_equal "$uuid" "$serial" && return ${DS_FOUND};; + nocase_equal "$uuid" "$serial" && + { _RET="AWS"; return 0; };; esac + _RET="$default" + return 0; +} + +dscheck_Ec2() { + check_seed_dir "ec2" meta-data user-data && return ${DS_FOUND} + is_container && return ${DS_NOT_FOUND} + + local unknown="Unknown" platform="" + if ec2_identify_platform "$unknown"; then + platform="$_RET" + else + warn "Failed to identify ec2 platform. Using '$unknown'." + platform=$unknown + fi + + debug 1 "ec2 platform is '$platform'." + if [ "$platform" != "$unknown" ]; then + return $DS_FOUND + fi + local default="true" if ec2_read_strict_setting "$default"; then strict="$_RET" -- cgit v1.2.3 From 5dd5b2cb539a84ed59f2b3181020d2bd18989718 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 24 Feb 2017 14:19:20 -0500 Subject: Identify Brightbox as an Ec2 datasource user. Brightbox will identify their platform to the guest by setting the product serial to a string that ends with 'brightbox.com'. LP: #1661693 --- cloudinit/sources/DataSourceEc2.py | 8 +++++++- tools/ds-identify | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'cloudinit/sources') diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index 26da263a..c7df8060 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -30,6 +30,7 @@ STRICT_ID_DEFAULT = "warn" class Platforms(object): ALIYUN = "AliYun" AWS = "AWS" + BRIGHTBOX = "Brightbox" SEEDED = "Seeded" UNKNOWN = "Unknown" @@ -325,10 +326,15 @@ def identify_aws(data): return None +def identify_brightbox(data): + if data['serial'].endswith('brightbox.com'): + return Platforms.BRIGHTBOX + + def identify_platform(): # identify the platform and return an entry in Platforms. data = _collect_platform_data() - checks = (identify_aws, lambda x: Platforms.UNKNOWN) + checks = (identify_aws, identify_brightbox, lambda x: Platforms.UNKNOWN) for checker in checks: try: result = checker(data) diff --git a/tools/ds-identify b/tools/ds-identify index dfa856ff..c39956fc 100755 --- a/tools/ds-identify +++ b/tools/ds-identify @@ -639,6 +639,11 @@ ec2_identify_platform() { local default="$1" local serial="${DI_DMI_PRODUCT_SERIAL}" + # brightbox https://bugs.launchpad.net/cloud-init/+bug/1661693 + case "$serial" in + *brightbox.com) _RET="Brightbox"; return 0;; + esac + # AWS http://docs.aws.amazon.com/AWSEC2/ # latest/UserGuide/identify_ec2_instances.html local uuid="" hvuuid="$PATH_ROOT/sys/hypervisor/uuid" -- cgit v1.2.3 From ade8c2e0266b020089145075e8236b95c000a3cb Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 28 Feb 2017 17:14:45 -0500 Subject: Move warning functionality to cloudinit/warnings.py This moves the warning code that was added specifically for EC2 into a generic path at cloudinit/warnings.py. It also adds support for writing warning files into the warnings directory to be shown by Z99-cloudinit-warnings.sh. --- cloudinit/helpers.py | 1 + cloudinit/sources/DataSourceEc2.py | 47 ++------------- cloudinit/warnings.py | 115 +++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 42 deletions(-) create mode 100644 cloudinit/warnings.py (limited to 'cloudinit/sources') diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py index 38f5f899..7435d58d 100644 --- a/cloudinit/helpers.py +++ b/cloudinit/helpers.py @@ -340,6 +340,7 @@ class Paths(object): "vendordata": "vendor-data.txt.i", "instance_id": ".instance-id", "manual_clean_marker": "manual-clean", + "warnings": "warnings", } # Set when a datasource becomes active self.datasource = ds diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index c7df8060..6f01a139 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -9,7 +9,6 @@ # This file is part of cloud-init. See LICENSE file for license information. import os -import textwrap import time from cloudinit import ec2_utils as ec2 @@ -17,6 +16,7 @@ from cloudinit import log as logging from cloudinit import sources from cloudinit import url_helper as uhelp from cloudinit import util +from cloudinit import warnings LOG = logging.getLogger(__name__) @@ -224,7 +224,8 @@ class DataSourceEc2(sources.DataSource): return if self.cloud_platform == Platforms.UNKNOWN: warn_if_necessary( - util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT)) + util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT), + cfg) def read_strict_mode(cfgval, default): @@ -265,7 +266,7 @@ def parse_strict_mode(cfgval): return mode, sleep -def warn_if_necessary(cfgval): +def warn_if_necessary(cfgval, cfg): try: mode, sleep = parse_strict_mode(cfgval) except ValueError as e: @@ -275,45 +276,7 @@ def warn_if_necessary(cfgval): if mode == "false": return - show_warning(sleep) - - -def show_warning(sleep): - message = textwrap.dedent(""" - **************************************************************** - # This system is using the EC2 Metadata Service, but does not # - # appear to be running on Amazon EC2 or one of cloud-init's # - # known platforms that provide a EC2 Metadata service. In the # - # future, cloud-init may 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. # - # # - # For more information see # - # https://bugs.launchpad.net/cloud-init/+bug/1660385 # - # # - # After you have filed a bug, you can disable this warning by # - # launching your instance with the cloud-config below, or # - # putting that content into # - # /etc/cloud/cloud.cfg.d/99-ec2-datasource.cfg # - # # - # #cloud-config # - # datasource: # - # Ec2: # - # strict_id: false # - # # - """) - closemsg = "" - if sleep: - closemsg = " [sleeping for %d seconds] " % sleep - message += closemsg.center(64, "*") - print(message) - LOG.warn(message) - if sleep: - time.sleep(sleep) + warnings.show_warning('non_ec2_md', cfg, mode=True, sleep=sleep) def identify_aws(data): diff --git a/cloudinit/warnings.py b/cloudinit/warnings.py new file mode 100644 index 00000000..77c092f9 --- /dev/null +++ b/cloudinit/warnings.py @@ -0,0 +1,115 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +from cloudinit import helpers +from cloudinit import log as logging +from cloudinit import util + +import os +import time + +LOG = logging.getLogger() + +WARNINGS = { + 'non_ec2_md': """ +This system is using the EC2 Metadata Service, but does not appear to +be running on Amazon EC2 or one of cloud-init's known platforms that +provide a EC2 Metadata service. In the future, cloud-init may 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?field.tags=dsid +Make sure to include the cloud provider your instance is +running on. + +For more information see + https://bugs.launchpad.net/bugs/1660385 + +After you have filed a bug, you can disable this warning by +launching your instance with the cloud-config below, or +putting that content into + /etc/cloud/cloud.cfg.d/99-ec2-datasource.cfg + +#cloud-config +datasource: + Ec2: + strict_id: false""", +} + + +def _get_warn_dir(cfg): + paths = helpers.Paths( + path_cfgs=cfg.get('system_info', {}).get('paths', {})) + return paths.get_ipath_cur('warnings') + + +def _load_warn_cfg(cfg, name, mode=True, sleep=None): + # parse cfg['warnings']['name'] returning boolean, sleep + # expected value is form of: + # (on|off|true|false|sleep)[,sleeptime] + # boolean True == on, False == off + default = (mode, sleep) + if not cfg or not isinstance(cfg, dict): + return default + + ncfg = util.get_cfg_by_path(cfg, ('warnings', name)) + if ncfg is None: + return default + + if ncfg in ("on", "true", True): + return True, None + + if ncfg in ("off", "false", False): + return False, None + + mode, _, csleep = ncfg.partition(",") + if mode != "sleep": + return default + + if csleep: + try: + sleep = int(csleep) + except ValueError: + return default + + return True, sleep + + +def show_warning(name, cfg=None, sleep=None, mode=True, **kwargs): + # kwargs are used for .format of the message. + # sleep and mode are default values used if + # cfg['warnings']['name'] is not present. + if cfg is None: + cfg = {} + + mode, sleep = _load_warn_cfg(cfg, name, mode=mode, sleep=sleep) + if not mode: + return + + msg = WARNINGS[name].format(**kwargs) + msgwidth = 70 + linewidth = msgwidth + 4 + + fmt = "# %%-%ds #" % msgwidth + topline = "*" * linewidth + "\n" + fmtlines = [] + for line in msg.strip("\n").splitlines(): + fmtlines.append(fmt % line) + + closeline = topline + if sleep: + sleepmsg = " [sleeping for %d seconds] " % sleep + closeline = sleepmsg.center(linewidth, "*") + "\n" + + util.write_file( + os.path.join(_get_warn_dir(cfg), name), + topline + "\n".join(fmtlines) + "\n" + topline) + + LOG.warn(topline + "\n".join(fmtlines) + "\n" + closeline) + + if sleep: + LOG.debug("sleeping %d seconds for warning '%s'" % (sleep, name)) + time.sleep(sleep) + +# vi: ts=4 expandtab -- cgit v1.2.3