summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChad Smith <chad.smith@canonical.com>2018-10-09 21:46:35 +0000
committerServer Team CI Bot <josh.powers+server-team-bot@canonical.com>2018-10-09 21:46:35 +0000
commitf0bc02d7e221c9aa5982b267739481420c761ead (patch)
treefb45cab8d2e9a44564eea23db18b0c3566fabe2a
parent00e36d3ded0b0f81f352de993036fc9f89e14a7a (diff)
downloadvyos-cloud-init-f0bc02d7e221c9aa5982b267739481420c761ead.tar.gz
vyos-cloud-init-f0bc02d7e221c9aa5982b267739481420c761ead.zip
instance-data: Add standard keys platform and subplatform. Refactor ec2.
Add the following instance-data.json standardized keys: * v1._beta_keys: List any v1 keys in beta development, e.g. ['subplatform']. * v1.public_ssh_keys: List of any cloud-provided ssh keys for the instance. * v1.platform: String representing the cloud platform api supporting the datasource. For example: 'ec2' for aws, aliyun and brightbox cloud names. * v1.subplatform: String with more details about the source of the metadata consumed. For example, metadata uri, config drive device path or seed directory. To support the new platform and subplatform standardized instance-data, DataSource and its subclasses grew platform and subplatform attributes. The platform attribute defaults to the lowercase string datasource name at self.dsname. This method is overridden in NoCloud, Ec2 and ConfigDrive datasources. The subplatform attribute calls a _get_subplatform method which will return a string containing a simple slug for subplatform type such as metadata, seed-dir or config-drive followed by a detailed uri, device or directory path where the datasource consumed its configuration. As part of this work, DatasourceEC2 methods _get_data and _crawl_metadata have been refactored for a few reasons: - crawl_metadata is now a read-only operation, persisting no attributes on the datasource instance and returns a dictionary of consumed metadata. - crawl_metadata now closely represents the raw stucture of the ec2 metadata consumed, so that end-users can leverage public ec2 metadata documentation where possible. - crawl_metadata adds a '_metadata_api_version' key to the crawled ds.metadata to advertise what version of EC2's api was consumed by cloud-init. - _get_data now does all the processing of crawl_metadata and saves datasource instance attributes userdata_raw, metadata etc. Additional drive-bys: * unit test rework for test_altcloud and test_azure to simplify mocks and make use of existing util and test_helpers functions.
-rw-r--r--cloudinit/sources/DataSourceAliYun.py20
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py33
-rw-r--r--cloudinit/sources/DataSourceAzure.py8
-rw-r--r--cloudinit/sources/DataSourceBigstep.py4
-rw-r--r--cloudinit/sources/DataSourceCloudSigma.py6
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py12
-rw-r--r--cloudinit/sources/DataSourceEc2.py115
-rw-r--r--cloudinit/sources/DataSourceIBMCloud.py4
-rw-r--r--cloudinit/sources/DataSourceMAAS.py4
-rw-r--r--cloudinit/sources/DataSourceNoCloud.py21
-rw-r--r--cloudinit/sources/DataSourceNone.py4
-rw-r--r--cloudinit/sources/DataSourceOVF.py6
-rw-r--r--cloudinit/sources/DataSourceOpenNebula.py8
-rw-r--r--cloudinit/sources/DataSourceOracle.py4
-rw-r--r--cloudinit/sources/DataSourceSmartOS.py3
-rw-r--r--cloudinit/sources/__init__.py98
-rw-r--r--cloudinit/sources/tests/test_init.py12
-rw-r--r--cloudinit/sources/tests/test_oracle.py8
-rw-r--r--cloudinit/tests/test_util.py16
-rw-r--r--cloudinit/util.py5
-rw-r--r--doc/rtd/topics/instancedata.rst183
-rw-r--r--tests/cloud_tests/testcases/base.py13
-rw-r--r--tests/unittests/test_datasource/test_aliyun.py4
-rw-r--r--tests/unittests/test_datasource/test_altcloud.py118
-rw-r--r--tests/unittests/test_datasource/test_azure.py110
-rw-r--r--tests/unittests/test_datasource/test_cloudsigma.py6
-rw-r--r--tests/unittests/test_datasource/test_configdrive.py3
-rw-r--r--tests/unittests/test_datasource/test_ec2.py20
-rw-r--r--tests/unittests/test_datasource/test_ibmcloud.py40
-rw-r--r--tests/unittests/test_datasource/test_nocloud.py45
-rw-r--r--tests/unittests/test_datasource/test_opennebula.py4
-rw-r--r--tests/unittests/test_datasource/test_ovf.py52
-rw-r--r--tests/unittests/test_datasource/test_smartos.py7
33 files changed, 722 insertions, 274 deletions
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
index 858e0827..45cc9f00 100644
--- a/cloudinit/sources/DataSourceAliYun.py
+++ b/cloudinit/sources/DataSourceAliYun.py
@@ -1,7 +1,5 @@
# This file is part of cloud-init. See LICENSE file for license information.
-import os
-
from cloudinit import sources
from cloudinit.sources import DataSourceEc2 as EC2
from cloudinit import util
@@ -18,25 +16,17 @@ class DataSourceAliYun(EC2.DataSourceEc2):
min_metadata_version = '2016-01-01'
extended_metadata_versions = []
- def __init__(self, sys_cfg, distro, paths):
- super(DataSourceAliYun, self).__init__(sys_cfg, distro, paths)
- self.seed_dir = os.path.join(paths.seed_dir, "AliYun")
-
def get_hostname(self, fqdn=False, resolve_ip=False, metadata_only=False):
return self.metadata.get('hostname', 'localhost.localdomain')
def get_public_ssh_keys(self):
return parse_public_keys(self.metadata.get('public-keys', {}))
- @property
- def cloud_platform(self):
- if self._cloud_platform is None:
- if _is_aliyun():
- self._cloud_platform = EC2.Platforms.ALIYUN
- else:
- self._cloud_platform = EC2.Platforms.NO_EC2_METADATA
-
- return self._cloud_platform
+ def _get_cloud_name(self):
+ if _is_aliyun():
+ return EC2.CloudNames.ALIYUN
+ else:
+ return EC2.CloudNames.NO_EC2_METADATA
def _is_aliyun():
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index 8cd312d0..5270fda8 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -89,7 +89,9 @@ class DataSourceAltCloud(sources.DataSource):
'''
Description:
Get the type for the cloud back end this instance is running on
- by examining the string returned by reading the dmi data.
+ by examining the string returned by reading either:
+ CLOUD_INFO_FILE or
+ the dmi data.
Input:
None
@@ -99,7 +101,14 @@ class DataSourceAltCloud(sources.DataSource):
'RHEV', 'VSPHERE' or 'UNKNOWN'
'''
-
+ if os.path.exists(CLOUD_INFO_FILE):
+ try:
+ cloud_type = util.load_file(CLOUD_INFO_FILE).strip().upper()
+ except IOError:
+ util.logexc(LOG, 'Unable to access cloud info file at %s.',
+ CLOUD_INFO_FILE)
+ return 'UNKNOWN'
+ return cloud_type
system_name = util.read_dmi_data("system-product-name")
if not system_name:
return 'UNKNOWN'
@@ -134,15 +143,7 @@ class DataSourceAltCloud(sources.DataSource):
LOG.debug('Invoked get_data()')
- if os.path.exists(CLOUD_INFO_FILE):
- try:
- cloud_type = util.load_file(CLOUD_INFO_FILE).strip().upper()
- except IOError:
- util.logexc(LOG, 'Unable to access cloud info file at %s.',
- CLOUD_INFO_FILE)
- return False
- else:
- cloud_type = self.get_cloud_type()
+ cloud_type = self.get_cloud_type()
LOG.debug('cloud_type: %s', str(cloud_type))
@@ -161,6 +162,15 @@ class DataSourceAltCloud(sources.DataSource):
util.logexc(LOG, 'Failed accessing user data.')
return False
+ def _get_subplatform(self):
+ """Return the subplatform metadata details."""
+ cloud_type = self.get_cloud_type()
+ if not hasattr(self, 'source'):
+ self.source = sources.METADATA_UNKNOWN
+ if cloud_type == 'RHEV':
+ self.source = '/dev/fd0'
+ return '%s (%s)' % (cloud_type.lower(), self.source)
+
def user_data_rhevm(self):
'''
RHEVM specific userdata read
@@ -232,6 +242,7 @@ class DataSourceAltCloud(sources.DataSource):
try:
return_str = util.mount_cb(cdrom_dev, read_user_data_callback)
if return_str:
+ self.source = cdrom_dev
break
except OSError as err:
if err.errno != errno.ENOENT:
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 783445e1..39391d01 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -351,6 +351,14 @@ class DataSourceAzure(sources.DataSource):
metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files)
return metadata
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ if self.seed.startswith('/dev'):
+ subplatform_type = 'config-disk'
+ else:
+ subplatform_type = 'seed-dir'
+ return '%s (%s)' % (subplatform_type, self.seed)
+
def crawl_metadata(self):
"""Walk all instance metadata sources returning a dict on success.
diff --git a/cloudinit/sources/DataSourceBigstep.py b/cloudinit/sources/DataSourceBigstep.py
index 699a85b5..52fff20a 100644
--- a/cloudinit/sources/DataSourceBigstep.py
+++ b/cloudinit/sources/DataSourceBigstep.py
@@ -36,6 +36,10 @@ class DataSourceBigstep(sources.DataSource):
self.userdata_raw = decoded["userdata_raw"]
return True
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ return 'metadata (%s)' % get_url_from_file()
+
def get_url_from_file():
try:
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index c816f349..2955d3f0 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -7,7 +7,7 @@
from base64 import b64decode
import re
-from cloudinit.cs_utils import Cepko
+from cloudinit.cs_utils import Cepko, SERIAL_PORT
from cloudinit import log as logging
from cloudinit import sources
@@ -84,6 +84,10 @@ class DataSourceCloudSigma(sources.DataSource):
return True
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ return 'cepko (%s)' % SERIAL_PORT
+
def get_hostname(self, fqdn=False, resolve_ip=False, metadata_only=False):
"""
Cleans up and uses the server's name if the latter is set. Otherwise
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index 664dc4b7..564e3eb3 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -160,6 +160,18 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
LOG.debug("no network configuration available")
return self._network_config
+ @property
+ def platform(self):
+ return 'openstack'
+
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ if self.seed_dir in self.source:
+ subplatform_type = 'seed-dir'
+ elif self.source.startswith('/dev'):
+ subplatform_type = 'config-disk'
+ return '%s (%s)' % (subplatform_type, self.source)
+
def read_config_drive(source_dir):
reader = openstack.ConfigDriveReader(source_dir)
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 968ab3f7..9ccf2cdc 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -28,18 +28,16 @@ STRICT_ID_PATH = ("datasource", "Ec2", "strict_id")
STRICT_ID_DEFAULT = "warn"
-class Platforms(object):
- # TODO Rename and move to cloudinit.cloud.CloudNames
- ALIYUN = "AliYun"
- AWS = "AWS"
- BRIGHTBOX = "Brightbox"
- SEEDED = "Seeded"
+class CloudNames(object):
+ ALIYUN = "aliyun"
+ AWS = "aws"
+ BRIGHTBOX = "brightbox"
# UNKNOWN indicates no positive id. If strict_id is 'warn' or 'false',
# then an attempt at the Ec2 Metadata service will be made.
- UNKNOWN = "Unknown"
+ UNKNOWN = "unknown"
# NO_EC2_METADATA indicates this platform does not have a Ec2 metadata
# service available. No attempt at the Ec2 Metadata service will be made.
- NO_EC2_METADATA = "No-EC2-Metadata"
+ NO_EC2_METADATA = "no-ec2-metadata"
class DataSourceEc2(sources.DataSource):
@@ -61,8 +59,6 @@ class DataSourceEc2(sources.DataSource):
url_max_wait = 120
url_timeout = 50
- _cloud_platform = None
-
_network_config = sources.UNSET # Used to cache calculated network cfg v1
# Whether we want to get network configuration from the metadata service.
@@ -71,30 +67,21 @@ class DataSourceEc2(sources.DataSource):
def __init__(self, sys_cfg, distro, paths):
super(DataSourceEc2, self).__init__(sys_cfg, distro, paths)
self.metadata_address = None
- self.seed_dir = os.path.join(paths.seed_dir, "ec2")
def _get_cloud_name(self):
"""Return the cloud name as identified during _get_data."""
- return self.cloud_platform
+ return identify_platform()
def _get_data(self):
- seed_ret = {}
- if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
- 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:
+ LOG.debug("strict_mode: %s, cloud_name=%s cloud_platform=%s",
+ strict_mode, self.cloud_name, self.platform)
+ if strict_mode == "true" and self.cloud_name == CloudNames.UNKNOWN:
return False
- elif self.cloud_platform == Platforms.NO_EC2_METADATA:
+ elif self.cloud_name == CloudNames.NO_EC2_METADATA:
return False
if self.perform_dhcp_setup: # Setup networking in init-local stage.
@@ -103,13 +90,22 @@ class DataSourceEc2(sources.DataSource):
return False
try:
with EphemeralDHCPv4(self.fallback_interface):
- return util.log_time(
+ self._crawled_metadata = util.log_time(
logfunc=LOG.debug, msg='Crawl of metadata service',
- func=self._crawl_metadata)
+ func=self.crawl_metadata)
except NoDHCPLeaseError:
return False
else:
- return self._crawl_metadata()
+ self._crawled_metadata = util.log_time(
+ logfunc=LOG.debug, msg='Crawl of metadata service',
+ func=self.crawl_metadata)
+ if not self._crawled_metadata:
+ return False
+ self.metadata = self._crawled_metadata.get('meta-data', None)
+ self.userdata_raw = self._crawled_metadata.get('user-data', None)
+ self.identity = self._crawled_metadata.get(
+ 'dynamic', {}).get('instance-identity', {}).get('document', {})
+ return True
@property
def launch_index(self):
@@ -117,6 +113,15 @@ class DataSourceEc2(sources.DataSource):
return None
return self.metadata.get('ami-launch-index')
+ @property
+ def platform(self):
+ # Handle upgrade path of pickled ds
+ if not hasattr(self, '_platform_type'):
+ self._platform_type = DataSourceEc2.dsname.lower()
+ if not self._platform_type:
+ self._platform_type = DataSourceEc2.dsname.lower()
+ return self._platform_type
+
def get_metadata_api_version(self):
"""Get the best supported api version from the metadata service.
@@ -144,7 +149,7 @@ class DataSourceEc2(sources.DataSource):
return self.min_metadata_version
def get_instance_id(self):
- if self.cloud_platform == Platforms.AWS:
+ if self.cloud_name == CloudNames.AWS:
# Prefer the ID from the instance identity document, but fall back
if not getattr(self, 'identity', None):
# If re-using cached datasource, it's get_data run didn't
@@ -254,7 +259,7 @@ class DataSourceEc2(sources.DataSource):
@property
def availability_zone(self):
try:
- if self.cloud_platform == Platforms.AWS:
+ if self.cloud_name == CloudNames.AWS:
return self.identity.get(
'availabilityZone',
self.metadata['placement']['availability-zone'])
@@ -265,7 +270,7 @@ class DataSourceEc2(sources.DataSource):
@property
def region(self):
- if self.cloud_platform == Platforms.AWS:
+ if self.cloud_name == CloudNames.AWS:
region = self.identity.get('region')
# Fallback to trimming the availability zone if region is missing
if self.availability_zone and not region:
@@ -277,16 +282,10 @@ class DataSourceEc2(sources.DataSource):
return az[:-1]
return None
- @property
- def cloud_platform(self): # TODO rename cloud_name
- 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:
+ if self.cloud_name == CloudNames.UNKNOWN:
warn_if_necessary(
util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT),
cfg)
@@ -306,13 +305,13 @@ class DataSourceEc2(sources.DataSource):
result = None
no_network_metadata_on_aws = bool(
'network' not in self.metadata and
- self.cloud_platform == Platforms.AWS)
+ self.cloud_name == CloudNames.AWS)
if no_network_metadata_on_aws:
LOG.debug("Metadata 'network' not present:"
" Refreshing stale metadata from prior to upgrade.")
util.log_time(
logfunc=LOG.debug, msg='Re-crawl of metadata service',
- func=self._crawl_metadata)
+ func=self.get_data)
# Limit network configuration to only the primary/fallback nic
iface = self.fallback_interface
@@ -340,28 +339,32 @@ class DataSourceEc2(sources.DataSource):
return super(DataSourceEc2, self).fallback_interface
return self._fallback_interface
- def _crawl_metadata(self):
+ def crawl_metadata(self):
"""Crawl metadata service when available.
- @returns: True on success, False otherwise.
+ @returns: Dictionary of crawled metadata content containing the keys:
+ meta-data, user-data and dynamic.
"""
if not self.wait_for_metadata_service():
- return False
+ return {}
api_version = self.get_metadata_api_version()
+ crawled_metadata = {}
try:
- self.userdata_raw = ec2.get_instance_userdata(
+ crawled_metadata['user-data'] = ec2.get_instance_userdata(
api_version, self.metadata_address)
- self.metadata = ec2.get_instance_metadata(
+ crawled_metadata['meta-data'] = ec2.get_instance_metadata(
api_version, self.metadata_address)
- if self.cloud_platform == Platforms.AWS:
- self.identity = ec2.get_instance_identity(
- api_version, self.metadata_address).get('document', {})
+ if self.cloud_name == CloudNames.AWS:
+ identity = ec2.get_instance_identity(
+ api_version, self.metadata_address)
+ crawled_metadata['dynamic'] = {'instance-identity': identity}
except Exception:
util.logexc(
LOG, "Failed reading from metadata address %s",
self.metadata_address)
- return False
- return True
+ return {}
+ crawled_metadata['_metadata_api_version'] = api_version
+ return crawled_metadata
class DataSourceEc2Local(DataSourceEc2):
@@ -375,10 +378,10 @@ class DataSourceEc2Local(DataSourceEc2):
perform_dhcp_setup = True # Use dhcp before querying metadata
def get_data(self):
- supported_platforms = (Platforms.AWS,)
- if self.cloud_platform not in supported_platforms:
+ supported_platforms = (CloudNames.AWS,)
+ if self.cloud_name not in supported_platforms:
LOG.debug("Local Ec2 mode only supported on %s, not %s",
- supported_platforms, self.cloud_platform)
+ supported_platforms, self.cloud_name)
return False
return super(DataSourceEc2Local, self).get_data()
@@ -439,20 +442,20 @@ def identify_aws(data):
if (data['uuid'].startswith('ec2') and
(data['uuid_source'] == 'hypervisor' or
data['uuid'] == data['serial'])):
- return Platforms.AWS
+ return CloudNames.AWS
return None
def identify_brightbox(data):
if data['serial'].endswith('brightbox.com'):
- return Platforms.BRIGHTBOX
+ return CloudNames.BRIGHTBOX
def identify_platform():
- # identify the platform and return an entry in Platforms.
+ # identify the platform and return an entry in CloudNames.
data = _collect_platform_data()
- checks = (identify_aws, identify_brightbox, lambda x: Platforms.UNKNOWN)
+ checks = (identify_aws, identify_brightbox, lambda x: CloudNames.UNKNOWN)
for checker in checks:
try:
result = checker(data)
diff --git a/cloudinit/sources/DataSourceIBMCloud.py b/cloudinit/sources/DataSourceIBMCloud.py
index a5358148..21e6ae6b 100644
--- a/cloudinit/sources/DataSourceIBMCloud.py
+++ b/cloudinit/sources/DataSourceIBMCloud.py
@@ -157,6 +157,10 @@ class DataSourceIBMCloud(sources.DataSource):
return True
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ return '%s (%s)' % (self.platform, self.source)
+
def check_instance_id(self, sys_cfg):
"""quickly (local check only) if self.instance_id is still valid
diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py
index bcb38544..61aa6d7e 100644
--- a/cloudinit/sources/DataSourceMAAS.py
+++ b/cloudinit/sources/DataSourceMAAS.py
@@ -109,6 +109,10 @@ class DataSourceMAAS(sources.DataSource):
LOG.warning("Invalid content in vendor-data: %s", e)
self.vendordata_raw = None
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ return 'seed-dir (%s)' % self.base_url
+
def wait_for_metadata_service(self, url):
mcfg = self.ds_cfg
max_wait = 120
diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
index 2daea59d..9010f06c 100644
--- a/cloudinit/sources/DataSourceNoCloud.py
+++ b/cloudinit/sources/DataSourceNoCloud.py
@@ -186,6 +186,27 @@ class DataSourceNoCloud(sources.DataSource):
self._network_eni = mydata['meta-data'].get('network-interfaces')
return True
+ @property
+ def platform_type(self):
+ # Handle upgrade path of pickled ds
+ if not hasattr(self, '_platform_type'):
+ self._platform_type = None
+ if not self._platform_type:
+ self._platform_type = 'lxd' if util.is_lxd() else 'nocloud'
+ return self._platform_type
+
+ def _get_cloud_name(self):
+ """Return unknown when 'cloud-name' key is absent from metadata."""
+ return sources.METADATA_UNKNOWN
+
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ if self.seed.startswith('/dev'):
+ subplatform_type = 'config-disk'
+ else:
+ subplatform_type = 'seed-dir'
+ return '%s (%s)' % (subplatform_type, self.seed)
+
def check_instance_id(self, sys_cfg):
# quickly (local check only) if self.instance_id is still valid
# we check kernel command line or files.
diff --git a/cloudinit/sources/DataSourceNone.py b/cloudinit/sources/DataSourceNone.py
index e63a7e39..e6250801 100644
--- a/cloudinit/sources/DataSourceNone.py
+++ b/cloudinit/sources/DataSourceNone.py
@@ -28,6 +28,10 @@ class DataSourceNone(sources.DataSource):
self.metadata = self.ds_cfg['metadata']
return True
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ return 'config'
+
def get_instance_id(self):
return 'iid-datasource-none'
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index 178ccb0f..045291e7 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -275,6 +275,12 @@ class DataSourceOVF(sources.DataSource):
self.cfg = cfg
return True
+ def _get_subplatform(self):
+ system_type = util.read_dmi_data("system-product-name").lower()
+ if system_type == 'vmware':
+ return 'vmware (%s)' % self.seed
+ return 'ovf (%s)' % self.seed
+
def get_public_ssh_keys(self):
if 'public-keys' not in self.metadata:
return []
diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py
index 77ccd128..e62e9729 100644
--- a/cloudinit/sources/DataSourceOpenNebula.py
+++ b/cloudinit/sources/DataSourceOpenNebula.py
@@ -95,6 +95,14 @@ class DataSourceOpenNebula(sources.DataSource):
self.userdata_raw = results.get('userdata')
return True
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ if self.seed_dir in self.seed:
+ subplatform_type = 'seed-dir'
+ else:
+ subplatform_type = 'config-disk'
+ return '%s (%s)' % (subplatform_type, self.seed)
+
@property
def network_config(self):
if self.network is not None:
diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py
index fab39af3..70b9c58a 100644
--- a/cloudinit/sources/DataSourceOracle.py
+++ b/cloudinit/sources/DataSourceOracle.py
@@ -91,6 +91,10 @@ class DataSourceOracle(sources.DataSource):
def crawl_metadata(self):
return read_metadata()
+ def _get_subplatform(self):
+ """Return the subplatform metadata source details."""
+ return 'metadata (%s)' % METADATA_ENDPOINT
+
def check_instance_id(self, sys_cfg):
"""quickly check (local only) if self.instance_id is still valid
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
index 593ac91a..32b57cdd 100644
--- a/cloudinit/sources/DataSourceSmartOS.py
+++ b/cloudinit/sources/DataSourceSmartOS.py
@@ -303,6 +303,9 @@ class DataSourceSmartOS(sources.DataSource):
self._set_provisioned()
return True
+ def _get_subplatform(self):
+ return 'serial (%s)' % SERIAL_DEVICE
+
def device_name_to_device(self, name):
return self.ds_cfg['disk_aliases'].get(name)
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 5ac98826..9b90680f 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -54,6 +54,7 @@ REDACT_SENSITIVE_VALUE = 'redacted for non-root user'
METADATA_CLOUD_NAME_KEY = 'cloud-name'
UNSET = "_unset"
+METADATA_UNKNOWN = 'unknown'
LOG = logging.getLogger(__name__)
@@ -133,6 +134,14 @@ class DataSource(object):
# Cached cloud_name as determined by _get_cloud_name
_cloud_name = None
+ # Cached cloud platform api type: e.g. ec2, openstack, kvm, lxd, azure etc.
+ _platform_type = None
+
+ # More details about the cloud platform:
+ # - metadata (http://169.254.169.254/)
+ # - seed-dir (<dirname>)
+ _subplatform = None
+
# Track the discovered fallback nic for use in configuration generation.
_fallback_interface = None
@@ -192,21 +201,24 @@ class DataSource(object):
local_hostname = self.get_hostname()
instance_id = self.get_instance_id()
availability_zone = self.availability_zone
- cloud_name = self.cloud_name
- # When adding new standard keys prefer underscore-delimited instead
- # of hyphen-delimted to support simple variable references in jinja
- # templates.
+ # In the event of upgrade from existing cloudinit, pickled datasource
+ # will not contain these new class attributes. So we need to recrawl
+ # metadata to discover that content.
return {
'v1': {
+ '_beta_keys': ['subplatform'],
'availability-zone': availability_zone,
'availability_zone': availability_zone,
- 'cloud-name': cloud_name,
- 'cloud_name': cloud_name,
+ 'cloud-name': self.cloud_name,
+ 'cloud_name': self.cloud_name,
+ 'platform': self.platform_type,
+ 'public_ssh_keys': self.get_public_ssh_keys(),
'instance-id': instance_id,
'instance_id': instance_id,
'local-hostname': local_hostname,
'local_hostname': local_hostname,
- 'region': self.region}}
+ 'region': self.region,
+ 'subplatform': self.subplatform}}
def clear_cached_attrs(self, attr_defaults=()):
"""Reset any cached metadata attributes to datasource defaults.
@@ -247,19 +259,27 @@ class DataSource(object):
@return True on successful write, False otherwise.
"""
- instance_data = {
- 'ds': {'_doc': EXPERIMENTAL_TEXT,
- 'meta_data': self.metadata}}
- if hasattr(self, 'network_json'):
- network_json = getattr(self, 'network_json')
- if network_json != UNSET:
- instance_data['ds']['network_json'] = network_json
- if hasattr(self, 'ec2_metadata'):
- ec2_metadata = getattr(self, 'ec2_metadata')
- if ec2_metadata != UNSET:
- instance_data['ds']['ec2_metadata'] = ec2_metadata
+ if hasattr(self, '_crawled_metadata'):
+ # Any datasource with _crawled_metadata will best represent
+ # most recent, 'raw' metadata
+ crawled_metadata = copy.deepcopy(
+ getattr(self, '_crawled_metadata'))
+ crawled_metadata.pop('user-data', None)
+ crawled_metadata.pop('vendor-data', None)
+ instance_data = {'ds': crawled_metadata}
+ else:
+ instance_data = {'ds': {'meta_data': self.metadata}}
+ if hasattr(self, 'network_json'):
+ network_json = getattr(self, 'network_json')
+ if network_json != UNSET:
+ instance_data['ds']['network_json'] = network_json
+ if hasattr(self, 'ec2_metadata'):
+ ec2_metadata = getattr(self, 'ec2_metadata')
+ if ec2_metadata != UNSET:
+ instance_data['ds']['ec2_metadata'] = ec2_metadata
instance_data.update(
self._get_standardized_metadata())
+ instance_data['ds']['_doc'] = EXPERIMENTAL_TEXT
try:
# Process content base64encoding unserializable values
content = util.json_dumps(instance_data)
@@ -347,6 +367,40 @@ class DataSource(object):
return self._fallback_interface
@property
+ def platform_type(self):
+ if not hasattr(self, '_platform_type'):
+ # Handle upgrade path where pickled datasource has no _platform.
+ self._platform_type = self.dsname.lower()
+ if not self._platform_type:
+ self._platform_type = self.dsname.lower()
+ return self._platform_type
+
+ @property
+ def subplatform(self):
+ """Return a string representing subplatform details for the datasource.
+
+ This should be guidance for where the metadata is sourced.
+ Examples of this on different clouds:
+ ec2: metadata (http://169.254.169.254)
+ openstack: configdrive (/dev/path)
+ openstack: metadata (http://169.254.169.254)
+ nocloud: seed-dir (/seed/dir/path)
+ lxd: nocloud (/seed/dir/path)
+ """
+ if not hasattr(self, '_subplatform'):
+ # Handle upgrade path where pickled datasource has no _platform.
+ self._subplatform = self._get_subplatform()
+ if not self._subplatform:
+ self._subplatform = self._get_subplatform()
+ return self._subplatform
+
+ def _get_subplatform(self):
+ """Subclasses should implement to return a "slug (detail)" string."""
+ if hasattr(self, 'metadata_address'):
+ return 'metadata (%s)' % getattr(self, 'metadata_address')
+ return METADATA_UNKNOWN
+
+ @property
def cloud_name(self):
"""Return lowercase cloud name as determined by the datasource.
@@ -359,9 +413,11 @@ class DataSource(object):
cloud_name = self.metadata.get(METADATA_CLOUD_NAME_KEY)
if isinstance(cloud_name, six.string_types):
self._cloud_name = cloud_name.lower()
- LOG.debug(
- 'Ignoring metadata provided key %s: non-string type %s',
- METADATA_CLOUD_NAME_KEY, type(cloud_name))
+ else:
+ self._cloud_name = self._get_cloud_name().lower()
+ LOG.debug(
+ 'Ignoring metadata provided key %s: non-string type %s',
+ METADATA_CLOUD_NAME_KEY, type(cloud_name))
else:
self._cloud_name = self._get_cloud_name().lower()
return self._cloud_name
diff --git a/cloudinit/sources/tests/test_init.py b/cloudinit/sources/tests/test_init.py
index 8082019e..391b3436 100644
--- a/cloudinit/sources/tests/test_init.py
+++ b/cloudinit/sources/tests/test_init.py
@@ -295,6 +295,7 @@ class TestDataSource(CiTestCase):
'base64_encoded_keys': [],
'sensitive_keys': [],
'v1': {
+ '_beta_keys': ['subplatform'],
'availability-zone': 'myaz',
'availability_zone': 'myaz',
'cloud-name': 'subclasscloudname',
@@ -303,7 +304,10 @@ class TestDataSource(CiTestCase):
'instance_id': 'iid-datasource',
'local-hostname': 'test-subclass-hostname',
'local_hostname': 'test-subclass-hostname',
- 'region': 'myregion'},
+ 'platform': 'mytestsubclass',
+ 'public_ssh_keys': [],
+ 'region': 'myregion',
+ 'subplatform': 'unknown'},
'ds': {
'_doc': EXPERIMENTAL_TEXT,
'meta_data': {'availability_zone': 'myaz',
@@ -339,6 +343,7 @@ class TestDataSource(CiTestCase):
'base64_encoded_keys': [],
'sensitive_keys': ['ds/meta_data/some/security-credentials'],
'v1': {
+ '_beta_keys': ['subplatform'],
'availability-zone': 'myaz',
'availability_zone': 'myaz',
'cloud-name': 'subclasscloudname',
@@ -347,7 +352,10 @@ class TestDataSource(CiTestCase):
'instance_id': 'iid-datasource',
'local-hostname': 'test-subclass-hostname',
'local_hostname': 'test-subclass-hostname',
- 'region': 'myregion'},
+ 'platform': 'mytestsubclass',
+ 'public_ssh_keys': [],
+ 'region': 'myregion',
+ 'subplatform': 'unknown'},
'ds': {
'_doc': EXPERIMENTAL_TEXT,
'meta_data': {
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
index 7599126c..97d62947 100644
--- a/cloudinit/sources/tests/test_oracle.py
+++ b/cloudinit/sources/tests/test_oracle.py
@@ -71,6 +71,14 @@ class TestDataSourceOracle(test_helpers.CiTestCase):
self.assertFalse(ds._get_data())
mocks._is_platform_viable.assert_called_once_with()
+ def test_platform_info(self):
+ """Return platform-related information for Oracle Datasource."""
+ ds, _mocks = self._get_ds()
+ self.assertEqual('oracle', ds.cloud_name)
+ self.assertEqual('oracle', ds.platform_type)
+ self.assertEqual(
+ 'metadata (http://169.254.169.254/openstack/)', ds.subplatform)
+
@mock.patch(DS_PATH + "._is_iscsi_root", return_value=True)
def test_without_userdata(self, m_is_iscsi_root):
"""If no user-data is provided, it should not be in return dict."""
diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
index edb0c18f..749a3846 100644
--- a/cloudinit/tests/test_util.py
+++ b/cloudinit/tests/test_util.py
@@ -478,4 +478,20 @@ class TestGetLinuxDistro(CiTestCase):
dist = util.get_linux_distro()
self.assertEqual(('foo', '1.1', 'aarch64'), dist)
+
+@mock.patch('os.path.exists')
+class TestIsLXD(CiTestCase):
+
+ def test_is_lxd_true_on_sock_device(self, m_exists):
+ """When lxd's /dev/lxd/sock exists, is_lxd returns true."""
+ m_exists.return_value = True
+ self.assertTrue(util.is_lxd())
+ m_exists.assert_called_once_with('/dev/lxd/sock')
+
+ def test_is_lxd_false_when_sock_device_absent(self, m_exists):
+ """When lxd's /dev/lxd/sock is absent, is_lxd returns false."""
+ m_exists.return_value = False
+ self.assertFalse(util.is_lxd())
+ m_exists.assert_called_once_with('/dev/lxd/sock')
+
# vi: ts=4 expandtab
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 50680960..c67d6be6 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -2171,6 +2171,11 @@ def is_container():
return False
+def is_lxd():
+ """Check to see if we are running in a lxd container."""
+ return os.path.exists('/dev/lxd/sock')
+
+
def get_proc_env(pid, encoding='utf-8', errors='replace'):
"""
Return the environment in a dict that a given process id was started with.
diff --git a/doc/rtd/topics/instancedata.rst b/doc/rtd/topics/instancedata.rst
index 634e1807..5d2dc948 100644
--- a/doc/rtd/topics/instancedata.rst
+++ b/doc/rtd/topics/instancedata.rst
@@ -90,24 +90,46 @@ There are three basic top-level keys:
The standardized keys present:
-+----------------------+-----------------------------------------------+---------------------------+
-| Key path | Description | Examples |
-+======================+===============================================+===========================+
-| v1.cloud_name | The name of the cloud provided by metadata | aws, openstack, azure, |
-| | key 'cloud-name' or the cloud-init datasource | configdrive, nocloud, |
-| | name which was discovered. | ovf, etc. |
-+----------------------+-----------------------------------------------+---------------------------+
-| v1.instance_id | Unique instance_id allocated by the cloud | i-<somehash> |
-+----------------------+-----------------------------------------------+---------------------------+
-| v1.local_hostname | The internal or local hostname of the system | ip-10-41-41-70, |
-| | | <user-provided-hostname> |
-+----------------------+-----------------------------------------------+---------------------------+
-| v1.region | The physical region/datacenter in which the | us-east-2 |
-| | instance is deployed | |
-+----------------------+-----------------------------------------------+---------------------------+
-| v1.availability_zone | The physical availability zone in which the | us-east-2b, nova, null |
-| | instance is deployed | |
-+----------------------+-----------------------------------------------+---------------------------+
++----------------------+-----------------------------------------------+-----------------------------------+
+| Key path | Description | Examples |
++======================+===============================================+===================================+
+| v1._beta_keys | List of standardized keys still in 'beta'. | [subplatform] |
+| | The format, intent or presence of these keys | |
+| | can change. Do not consider them | |
+| | production-ready. | |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.cloud_name | Where possible this will indicate the 'name' | aws, openstack, azure, |
+| | of the cloud this system is running on. This | configdrive, nocloud, |
+| | is specifically different than the 'platform' | ovf, etc. |
+| | below. As an example, the name of Amazon Web | |
+| | Services is 'aws' while the platform is 'ec2'.| |
+| | | |
+| | If no specific name is determinable or | |
+| | provided in meta-data, then this field may | |
+| | contain the same content as 'platform'. | |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.instance_id | Unique instance_id allocated by the cloud | i-<somehash> |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.local_hostname | The internal or local hostname of the system | ip-10-41-41-70, |
+| | | <user-provided-hostname> |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.platform | An attempt to identify the cloud platform | ec2, openstack, lxd, gce |
+| | instance that the system is running on. | nocloud, ovf |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.subplatform | Additional platform details describing the | metadata (http://168.254.169.254),|
+| | specific source or type of metadata used. | seed-dir (/path/to/seed-dir/), |
+| | The format of subplatform will be: | config-disk (/dev/cd0), |
+| | <subplatform_type> (<url_file_or_dev_path>) | configdrive (/dev/sr0) |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.public_ssh_keys | A list of ssh keys provided to the instance | ['ssh-rsa AA...', ...] |
+| | by the datasource metadata. | |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.region | The physical region/datacenter in which the | us-east-2 |
+| | instance is deployed | |
++----------------------+-----------------------------------------------+-----------------------------------+
+| v1.availability_zone | The physical availability zone in which the | us-east-2b, nova, null |
+| | instance is deployed | |
++----------------------+-----------------------------------------------+-----------------------------------+
Below is an example of ``/run/cloud-init/instance_data.json`` on an EC2
@@ -117,10 +139,75 @@ instance:
{
"base64_encoded_keys": [],
- "sensitive_keys": [],
"ds": {
- "meta_data": {
- "ami-id": "ami-014e1416b628b0cbf",
+ "_doc": "EXPERIMENTAL: The structure and format of content scoped under the 'ds' key may change in subsequent releases of cloud-init.",
+ "_metadata_api_version": "2016-09-02",
+ "dynamic": {
+ "instance-identity": {
+ "document": {
+ "accountId": "437526006925",
+ "architecture": "x86_64",
+ "availabilityZone": "us-east-2b",
+ "billingProducts": null,
+ "devpayProductCodes": null,
+ "imageId": "ami-079638aae7046bdd2",
+ "instanceId": "i-075f088c72ad3271c",
+ "instanceType": "t2.micro",
+ "kernelId": null,
+ "marketplaceProductCodes": null,
+ "pendingTime": "2018-10-05T20:10:43Z",
+ "privateIp": "10.41.41.95",
+ "ramdiskId": null,
+ "region": "us-east-2",
+ "version": "2017-09-30"
+ },
+ "pkcs7": [
+ "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCAJIAEggHbewog",
+ "ICJkZXZwYXlQcm9kdWN0Q29kZXMiIDogbnVsbCwKICAibWFya2V0cGxhY2VQcm9kdWN0Q29kZXMi",
+ "IDogbnVsbCwKICAicHJpdmF0ZUlwIiA6ICIxMC40MS40MS45NSIsCiAgInZlcnNpb24iIDogIjIw",
+ "MTctMDktMzAiLAogICJpbnN0YW5jZUlkIiA6ICJpLTA3NWYwODhjNzJhZDMyNzFjIiwKICAiYmls",
+ "bGluZ1Byb2R1Y3RzIiA6IG51bGwsCiAgImluc3RhbmNlVHlwZSIgOiAidDIubWljcm8iLAogICJh",
+ "Y2NvdW50SWQiIDogIjQzNzUyNjAwNjkyNSIsCiAgImF2YWlsYWJpbGl0eVpvbmUiIDogInVzLWVh",
+ "c3QtMmIiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJyYW1kaXNrSWQiIDogbnVsbCwKICAiYXJj",
+ "aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJpbWFnZUlkIiA6ICJhbWktMDc5NjM4YWFlNzA0NmJk",
+ "ZDIiLAogICJwZW5kaW5nVGltZSIgOiAiMjAxOC0xMC0wNVQyMDoxMDo0M1oiLAogICJyZWdpb24i",
+ "IDogInVzLWVhc3QtMiIKfQAAAAAAADGCARcwggETAgEBMGkwXDELMAkGA1UEBhMCVVMxGTAXBgNV",
+ "BAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBX",
+ "ZWIgU2VydmljZXMgTExDAgkAlrpI2eVeGmcwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkq",
+ "hkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE4MTAwNTIwMTA0OFowIwYJKoZIhvcNAQkEMRYEFK0k",
+ "Tz6n1A8/zU1AzFj0riNQORw2MAkGByqGSM44BAMELjAsAhRNrr174y98grPBVXUforN/6wZp8AIU",
+ "JLZBkrB2GJA8A4WJ1okq++jSrBIAAAAAAAA="
+ ],
+ "rsa2048": [
+ "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABIIB",
+ "23sKICAiZGV2cGF5UHJvZHVjdENvZGVzIiA6IG51bGwsCiAgIm1hcmtldHBsYWNlUHJvZHVjdENv",
+ "ZGVzIiA6IG51bGwsCiAgInByaXZhdGVJcCIgOiAiMTAuNDEuNDEuOTUiLAogICJ2ZXJzaW9uIiA6",
+ "ICIyMDE3LTA5LTMwIiwKICAiaW5zdGFuY2VJZCIgOiAiaS0wNzVmMDg4YzcyYWQzMjcxYyIsCiAg",
+ "ImJpbGxpbmdQcm9kdWN0cyIgOiBudWxsLAogICJpbnN0YW5jZVR5cGUiIDogInQyLm1pY3JvIiwK",
+ "ICAiYWNjb3VudElkIiA6ICI0Mzc1MjYwMDY5MjUiLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1",
+ "cy1lYXN0LTJiIiwKICAia2VybmVsSWQiIDogbnVsbCwKICAicmFtZGlza0lkIiA6IG51bGwsCiAg",
+ "ImFyY2hpdGVjdHVyZSIgOiAieDg2XzY0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLTA3OTYzOGFhZTcw",
+ "NDZiZGQyIiwKICAicGVuZGluZ1RpbWUiIDogIjIwMTgtMTAtMDVUMjA6MTA6NDNaIiwKICAicmVn",
+ "aW9uIiA6ICJ1cy1lYXN0LTIiCn0AAAAAAAAxggH/MIIB+wIBATBpMFwxCzAJBgNVBAYTAlVTMRkw",
+ "FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6",
+ "b24gV2ViIFNlcnZpY2VzIExMQwIJAM07oeX4xevdMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN",
+ "AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTgxMDA1MjAxMDQ4WjAvBgkqhkiG9w0B",
+ "CQQxIgQgkYz0pZk3zJKBi4KP4egeOKJl/UYwu5UdE7id74pmPwMwDQYJKoZIhvcNAQEBBQAEggEA",
+ "dC3uIGGNul1OC1mJKSH3XoBWsYH20J/xhIdftYBoXHGf2BSFsrs9ZscXd2rKAKea4pSPOZEYMXgz",
+ "lPuT7W0WU89N3ZKviy/ReMSRjmI/jJmsY1lea6mlgcsJXreBXFMYucZvyeWGHdnCjamoKWXkmZlM",
+ "mSB1gshWy8Y7DzoKviYPQZi5aI54XK2Upt4kGme1tH1NI2Cq+hM4K+adxTbNhS3uzvWaWzMklUuU",
+ "QHX2GMmjAVRVc8vnA8IAsBCJJp+gFgYzi09IK+cwNgCFFPADoG6jbMHHf4sLB3MUGpiA+G9JlCnM",
+ "fmkjI2pNRB8spc0k4UG4egqLrqCz67WuK38tjwAAAAAAAA=="
+ ],
+ "signature": [
+ "Tsw6h+V3WnxrNVSXBYIOs1V4j95YR1mLPPH45XnhX0/Ei3waJqf7/7EEKGYP1Cr4PTYEULtZ7Mvf",
+ "+xJpM50Ivs2bdF7o0c4vnplRWe3f06NI9pv50dr110j/wNzP4MZ1pLhJCqubQOaaBTF3LFutgRrt",
+ "r4B0mN3p7EcqD8G+ll0="
+ ]
+ }
+ },
+ "meta-data": {
+ "ami-id": "ami-079638aae7046bdd2",
"ami-launch-index": "0",
"ami-manifest-path": "(unknown)",
"block-device-mapping": {
@@ -129,31 +216,31 @@ instance:
"ephemeral1": "sdc",
"root": "/dev/sda1"
},
- "hostname": "ip-10-41-41-70.us-east-2.compute.internal",
+ "hostname": "ip-10-41-41-95.us-east-2.compute.internal",
"instance-action": "none",
- "instance-id": "i-04fa31cfc55aa7976",
+ "instance-id": "i-075f088c72ad3271c",
"instance-type": "t2.micro",
- "local-hostname": "ip-10-41-41-70.us-east-2.compute.internal",
- "local-ipv4": "10.41.41.70",
- "mac": "06:b6:92:dd:9d:24",
+ "local-hostname": "ip-10-41-41-95.us-east-2.compute.internal",
+ "local-ipv4": "10.41.41.95",
+ "mac": "06:74:8f:39:cd:a6",
"metrics": {
"vhostmd": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
},
"network": {
"interfaces": {
"macs": {
- "06:b6:92:dd:9d:24": {
+ "06:74:8f:39:cd:a6": {
"device-number": "0",
- "interface-id": "eni-08c0c9fdb99b6e6f4",
+ "interface-id": "eni-052058bbd7831eaae",
"ipv4-associations": {
- "18.224.22.43": "10.41.41.70"
+ "18.218.221.122": "10.41.41.95"
},
- "local-hostname": "ip-10-41-41-70.us-east-2.compute.internal",
- "local-ipv4s": "10.41.41.70",
- "mac": "06:b6:92:dd:9d:24",
+ "local-hostname": "ip-10-41-41-95.us-east-2.compute.internal",
+ "local-ipv4s": "10.41.41.95",
+ "mac": "06:74:8f:39:cd:a6",
"owner-id": "437526006925",
- "public-hostname": "ec2-18-224-22-43.us-east-2.compute.amazonaws.com",
- "public-ipv4s": "18.224.22.43",
+ "public-hostname": "ec2-18-218-221-122.us-east-2.compute.amazonaws.com",
+ "public-ipv4s": "18.218.221.122",
"security-group-ids": "sg-828247e9",
"security-groups": "Cloud-init integration test secgroup",
"subnet-id": "subnet-282f3053",
@@ -171,16 +258,14 @@ instance:
"availability-zone": "us-east-2b"
},
"profile": "default-hvm",
- "public-hostname": "ec2-18-224-22-43.us-east-2.compute.amazonaws.com",
- "public-ipv4": "18.224.22.43",
+ "public-hostname": "ec2-18-218-221-122.us-east-2.compute.amazonaws.com",
+ "public-ipv4": "18.218.221.122",
"public-keys": {
"cloud-init-integration": [
- "ssh-rsa
- AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj8cgWyIOaspgKdVy0cKJ+UTjfv7jBOjG2H/GN8bJVXy72XAvnhM0dUM+CCs8FOf0YlPX+Frvz2hKInrmRhZVwRSL129PasD12MlI3l44u6IwS1o/W86Q+tkQYEljtqDOo0a+cOsaZkvUNzUyEXUwz/lmYa6G4hMKZH4NBj7nbAAF96wsMCoyNwbWryBnDYUr6wMbjRR1J9Pw7Xh7WRC73wy4Va2YuOgbD3V/5ZrFPLbWZW/7TFXVrql04QVbyei4aiFR5n//GvoqwQDNe58LmbzX/xvxyKJYdny2zXmdAhMxbrpFQsfpkJ9E/H5w0yOdSvnWbUoG5xNGoOB
- cloud-init-integration"
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj8cgWyIOaspgKdVy0cKJ+UTjfv7jBOjG2H/GN8bJVXy72XAvnhM0dUM+CCs8FOf0YlPX+Frvz2hKInrmRhZVwRSL129PasD12MlI3l44u6IwS1o/W86Q+tkQYEljtqDOo0a+cOsaZkvUNzUyEXUwz/lmYa6G4hMKZH4NBj7nbAAF96wsMCoyNwbWryBnDYUr6wMbjRR1J9Pw7Xh7WRC73wy4Va2YuOgbD3V/5ZrFPLbWZW/7TFXVrql04QVbyei4aiFR5n//GvoqwQDNe58LmbzX/xvxyKJYdny2zXmdAhMxbrpFQsfpkJ9E/H5w0yOdSvnWbUoG5xNGoOB cloud-init-integration"
]
},
- "reservation-id": "r-06ab75e9346f54333",
+ "reservation-id": "r-0594a20e31f6cfe46",
"security-groups": "Cloud-init integration test secgroup",
"services": {
"domain": "amazonaws.com",
@@ -188,16 +273,22 @@ instance:
}
}
},
+ "sensitive_keys": [],
"v1": {
+ "_beta_keys": [
+ "subplatform"
+ ],
"availability-zone": "us-east-2b",
"availability_zone": "us-east-2b",
- "cloud-name": "aws",
"cloud_name": "aws",
- "instance-id": "i-04fa31cfc55aa7976",
- "instance_id": "i-04fa31cfc55aa7976",
- "local-hostname": "ip-10-41-41-70",
- "local_hostname": "ip-10-41-41-70",
- "region": "us-east-2"
+ "instance_id": "i-075f088c72ad3271c",
+ "local_hostname": "ip-10-41-41-95",
+ "platform": "ec2",
+ "public_ssh_keys": [
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj8cgWyIOaspgKdVy0cKJ+UTjfv7jBOjG2H/GN8bJVXy72XAvnhM0dUM+CCs8FOf0YlPX+Frvz2hKInrmRhZVwRSL129PasD12MlI3l44u6IwS1o/W86Q+tkQYEljtqDOo0a+cOsaZkvUNzUyEXUwz/lmYa6G4hMKZH4NBj7nbAAF96wsMCoyNwbWryBnDYUr6wMbjRR1J9Pw7Xh7WRC73wy4Va2YuOgbD3V/5ZrFPLbWZW/7TFXVrql04QVbyei4aiFR5n//GvoqwQDNe58LmbzX/xvxyKJYdny2zXmdAhMxbrpFQsfpkJ9E/H5w0yOdSvnWbUoG5xNGoOB cloud-init-integration"
+ ],
+ "region": "us-east-2",
+ "subplatform": "metadata (http://169.254.169.254)"
}
}
diff --git a/tests/cloud_tests/testcases/base.py b/tests/cloud_tests/testcases/base.py
index e18d601c..16b268ef 100644
--- a/tests/cloud_tests/testcases/base.py
+++ b/tests/cloud_tests/testcases/base.py
@@ -195,6 +195,9 @@ class CloudTestCase(unittest2.TestCase):
self.assertIsNotNone(
v1_data['availability_zone'], 'expected ec2 availability_zone')
self.assertEqual('aws', v1_data['cloud_name'])
+ self.assertEqual('ec2', v1_data['platform'])
+ self.assertEqual(
+ 'metadata (http://169.254.169.254)', v1_data['subplatform'])
self.assertIn('i-', v1_data['instance_id'])
self.assertIn('ip-', v1_data['local_hostname'])
self.assertIsNotNone(v1_data['region'], 'expected ec2 region')
@@ -220,7 +223,11 @@ class CloudTestCase(unittest2.TestCase):
instance_data = json.loads(out)
v1_data = instance_data.get('v1', {})
self.assertItemsEqual([], sorted(instance_data['base64_encoded_keys']))
- self.assertEqual('nocloud', v1_data['cloud_name'])
+ self.assertEqual('unknown', v1_data['cloud_name'])
+ self.assertEqual('lxd', v1_data['platform'])
+ self.assertEqual(
+ 'seed-dir (/var/lib/cloud/seed/nocloud-net)',
+ v1_data['subplatform'])
self.assertIsNone(
v1_data['availability_zone'],
'found unexpected lxd availability_zone %s' %
@@ -253,7 +260,9 @@ class CloudTestCase(unittest2.TestCase):
instance_data = json.loads(out)
v1_data = instance_data.get('v1', {})
self.assertItemsEqual([], instance_data['base64_encoded_keys'])
- self.assertEqual('nocloud', v1_data['cloud_name'])
+ self.assertEqual('unknown', v1_data['cloud_name'])
+ self.assertEqual('nocloud', v1_data['platform'])
+ self.assertEqual('config-disk (/dev/vda)', v1_data['subplatform'])
self.assertIsNone(
v1_data['availability_zone'],
'found unexpected kvm availability_zone %s' %
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index 1e77842f..e9213ca1 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -140,6 +140,10 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
self._test_get_sshkey()
self._test_get_iid()
self._test_host_name()
+ self.assertEqual('aliyun', self.ds.cloud_name)
+ self.assertEqual('ec2', self.ds.platform)
+ self.assertEqual(
+ 'metadata (http://100.100.100.200)', self.ds.subplatform)
@mock.patch("cloudinit.sources.DataSourceAliYun._is_aliyun")
def test_returns_false_when_not_on_aliyun(self, m_is_aliyun):
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index ff35904e..3119bfac 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -10,7 +10,6 @@
This test file exercises the code in sources DataSourceAltCloud.py
'''
-import mock
import os
import shutil
import tempfile
@@ -18,32 +17,13 @@ import tempfile
from cloudinit import helpers
from cloudinit import util
-from cloudinit.tests.helpers import CiTestCase
+from cloudinit.tests.helpers import CiTestCase, mock
import cloudinit.sources.DataSourceAltCloud as dsac
OS_UNAME_ORIG = getattr(os, 'uname')
-def _write_cloud_info_file(value):
- '''
- Populate the CLOUD_INFO_FILE which would be populated
- with a cloud backend identifier ImageFactory when building
- an image with ImageFactory.
- '''
- cifile = open(dsac.CLOUD_INFO_FILE, 'w')
- cifile.write(value)
- cifile.close()
- os.chmod(dsac.CLOUD_INFO_FILE, 0o664)
-
-
-def _remove_cloud_info_file():
- '''
- Remove the test CLOUD_INFO_FILE
- '''
- os.remove(dsac.CLOUD_INFO_FILE)
-
-
def _write_user_data_files(mount_dir, value):
'''
Populate the deltacloud_user_data_file the user_data_file
@@ -98,13 +78,15 @@ def _dmi_data(expected):
class TestGetCloudType(CiTestCase):
- '''
- Test to exercise method: DataSourceAltCloud.get_cloud_type()
- '''
+ '''Test to exercise method: DataSourceAltCloud.get_cloud_type()'''
+
+ with_logs = True
def setUp(self):
'''Set up.'''
- self.paths = helpers.Paths({'cloud_dir': '/tmp'})
+ super(TestGetCloudType, self).setUp()
+ self.tmp = self.tmp_dir()
+ self.paths = helpers.Paths({'cloud_dir': self.tmp})
self.dmi_data = util.read_dmi_data
# We have a different code path for arm to deal with LP1243287
# We have to switch arch to x86_64 to avoid test failure
@@ -115,6 +97,26 @@ class TestGetCloudType(CiTestCase):
util.read_dmi_data = self.dmi_data
force_arch()
+ def test_cloud_info_file_ioerror(self):
+ """Return UNKNOWN when /etc/sysconfig/cloud-info exists but errors."""
+ self.assertEqual('/etc/sysconfig/cloud-info', dsac.CLOUD_INFO_FILE)
+ dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
+ # Attempting to read the directory generates IOError
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', self.tmp):
+ self.assertEqual('UNKNOWN', dsrc.get_cloud_type())
+ self.assertIn(
+ "[Errno 21] Is a directory: '%s'" % self.tmp,
+ self.logs.getvalue())
+
+ def test_cloud_info_file(self):
+ """Return uppercase stripped content from /etc/sysconfig/cloud-info."""
+ dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
+ cloud_info = self.tmp_path('cloud-info', dir=self.tmp)
+ util.write_file(cloud_info, ' OverRiDdeN CloudType ')
+ # Attempting to read the directory generates IOError
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', cloud_info):
+ self.assertEqual('OVERRIDDEN CLOUDTYPE', dsrc.get_cloud_type())
+
def test_rhev(self):
'''
Test method get_cloud_type() for RHEVm systems.
@@ -153,60 +155,57 @@ class TestGetDataCloudInfoFile(CiTestCase):
self.tmp = self.tmp_dir()
self.paths = helpers.Paths(
{'cloud_dir': self.tmp, 'run_dir': self.tmp})
- self.cloud_info_file = tempfile.mkstemp()[1]
- self.dmi_data = util.read_dmi_data
- dsac.CLOUD_INFO_FILE = self.cloud_info_file
-
- def tearDown(self):
- # Reset
-
- # Attempt to remove the temp file ignoring errors
- try:
- os.remove(self.cloud_info_file)
- except OSError:
- pass
-
- util.read_dmi_data = self.dmi_data
- dsac.CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info'
+ self.cloud_info_file = self.tmp_path('cloud-info', dir=self.tmp)
def test_rhev(self):
'''Success Test module get_data() forcing RHEV.'''
- _write_cloud_info_file('RHEV')
+ util.write_file(self.cloud_info_file, 'RHEV')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_rhevm = lambda: True
- self.assertEqual(True, dsrc.get_data())
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', self.cloud_info_file):
+ self.assertEqual(True, dsrc.get_data())
+ self.assertEqual('altcloud', dsrc.cloud_name)
+ self.assertEqual('altcloud', dsrc.platform_type)
+ self.assertEqual('rhev (/dev/fd0)', dsrc.subplatform)
def test_vsphere(self):
'''Success Test module get_data() forcing VSPHERE.'''
- _write_cloud_info_file('VSPHERE')
+ util.write_file(self.cloud_info_file, 'VSPHERE')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_vsphere = lambda: True
- self.assertEqual(True, dsrc.get_data())
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', self.cloud_info_file):
+ self.assertEqual(True, dsrc.get_data())
+ self.assertEqual('altcloud', dsrc.cloud_name)
+ self.assertEqual('altcloud', dsrc.platform_type)
+ self.assertEqual('vsphere (unknown)', dsrc.subplatform)
def test_fail_rhev(self):
'''Failure Test module get_data() forcing RHEV.'''
- _write_cloud_info_file('RHEV')
+ util.write_file(self.cloud_info_file, 'RHEV')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_rhevm = lambda: False
- self.assertEqual(False, dsrc.get_data())
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', self.cloud_info_file):
+ self.assertEqual(False, dsrc.get_data())
def test_fail_vsphere(self):
'''Failure Test module get_data() forcing VSPHERE.'''
- _write_cloud_info_file('VSPHERE')
+ util.write_file(self.cloud_info_file, 'VSPHERE')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_vsphere = lambda: False
- self.assertEqual(False, dsrc.get_data())
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', self.cloud_info_file):
+ self.assertEqual(False, dsrc.get_data())
def test_unrecognized(self):
'''Failure Test module get_data() forcing unrecognized.'''
- _write_cloud_info_file('unrecognized')
+ util.write_file(self.cloud_info_file, 'unrecognized')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
- self.assertEqual(False, dsrc.get_data())
+ with mock.patch.object(dsac, 'CLOUD_INFO_FILE', self.cloud_info_file):
+ self.assertEqual(False, dsrc.get_data())
class TestGetDataNoCloudInfoFile(CiTestCase):
@@ -322,7 +321,8 @@ class TestUserDataVsphere(CiTestCase):
'''
def setUp(self):
'''Set up.'''
- self.paths = helpers.Paths({'cloud_dir': '/tmp'})
+ self.tmp = self.tmp_dir()
+ self.paths = helpers.Paths({'cloud_dir': self.tmp})
self.mount_dir = tempfile.mkdtemp()
_write_user_data_files(self.mount_dir, 'test user data')
@@ -363,6 +363,22 @@ class TestUserDataVsphere(CiTestCase):
self.assertEqual(1, m_find_devs_with.call_count)
self.assertEqual(1, m_mount_cb.call_count)
+ @mock.patch("cloudinit.sources.DataSourceAltCloud.util.find_devs_with")
+ @mock.patch("cloudinit.sources.DataSourceAltCloud.util.mount_cb")
+ def test_user_data_vsphere_success(self, m_mount_cb, m_find_devs_with):
+ """Test user_data_vsphere() where successful."""
+ m_find_devs_with.return_value = ["/dev/mock/cdrom"]
+ m_mount_cb.return_value = 'raw userdata from cdrom'
+ dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
+ cloud_info = self.tmp_path('cloud-info', dir=self.tmp)
+ util.write_file(cloud_info, 'VSPHERE')
+ self.assertEqual(True, dsrc.user_data_vsphere())
+ m_find_devs_with.assert_called_once_with('LABEL=CDROM')
+ m_mount_cb.assert_called_once_with(
+ '/dev/mock/cdrom', dsac.read_user_data_callback)
+ with mock.patch.object(dsrc, 'get_cloud_type', return_value='VSPHERE'):
+ self.assertEqual('vsphere (/dev/mock/cdrom)', dsrc.subplatform)
+
class TestReadUserDataCallback(CiTestCase):
'''
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 4e428b71..0f4b7bf7 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -110,6 +110,8 @@ NETWORK_METADATA = {
}
}
+MOCKPATH = 'cloudinit.sources.DataSourceAzure.'
+
class TestGetMetadataFromIMDS(HttprettyTestCase):
@@ -119,9 +121,9 @@ class TestGetMetadataFromIMDS(HttprettyTestCase):
super(TestGetMetadataFromIMDS, self).setUp()
self.network_md_url = dsaz.IMDS_URL + "instance?api-version=2017-12-01"
- @mock.patch('cloudinit.sources.DataSourceAzure.readurl')
- @mock.patch('cloudinit.sources.DataSourceAzure.EphemeralDHCPv4')
- @mock.patch('cloudinit.sources.DataSourceAzure.net.is_up')
+ @mock.patch(MOCKPATH + 'readurl')
+ @mock.patch(MOCKPATH + 'EphemeralDHCPv4')
+ @mock.patch(MOCKPATH + 'net.is_up')
def test_get_metadata_does_not_dhcp_if_network_is_up(
self, m_net_is_up, m_dhcp, m_readurl):
"""Do not perform DHCP setup when nic is already up."""
@@ -138,9 +140,9 @@ class TestGetMetadataFromIMDS(HttprettyTestCase):
"Crawl of Azure Instance Metadata Service (IMDS) took", # log_time
self.logs.getvalue())
- @mock.patch('cloudinit.sources.DataSourceAzure.readurl')
- @mock.patch('cloudinit.sources.DataSourceAzure.EphemeralDHCPv4')
- @mock.patch('cloudinit.sources.DataSourceAzure.net.is_up')
+ @mock.patch(MOCKPATH + 'readurl')
+ @mock.patch(MOCKPATH + 'EphemeralDHCPv4')
+ @mock.patch(MOCKPATH + 'net.is_up')
def test_get_metadata_performs_dhcp_when_network_is_down(
self, m_net_is_up, m_dhcp, m_readurl):
"""Perform DHCP setup when nic is not up."""
@@ -163,7 +165,7 @@ class TestGetMetadataFromIMDS(HttprettyTestCase):
headers={'Metadata': 'true'}, retries=2, timeout=1)
@mock.patch('cloudinit.url_helper.time.sleep')
- @mock.patch('cloudinit.sources.DataSourceAzure.net.is_up')
+ @mock.patch(MOCKPATH + 'net.is_up')
def test_get_metadata_from_imds_empty_when_no_imds_present(
self, m_net_is_up, m_sleep):
"""Return empty dict when IMDS network metadata is absent."""
@@ -380,7 +382,7 @@ fdescfs /dev/fd fdescfs rw 0 0
res = get_path_dev_freebsd('/etc', mnt_list)
self.assertIsNotNone(res)
- @mock.patch('cloudinit.sources.DataSourceAzure._is_platform_viable')
+ @mock.patch(MOCKPATH + '_is_platform_viable')
def test_call_is_platform_viable_seed(self, m_is_platform_viable):
"""Check seed_dir using _is_platform_viable and return False."""
# Return a non-matching asset tag value
@@ -401,6 +403,24 @@ fdescfs /dev/fd fdescfs rw 0 0
self.assertEqual(dsrc.metadata['local-hostname'], odata['HostName'])
self.assertTrue(os.path.isfile(
os.path.join(self.waagent_d, 'ovf-env.xml')))
+ self.assertEqual('azure', dsrc.cloud_name)
+ self.assertEqual('azure', dsrc.platform_type)
+ self.assertEqual(
+ 'seed-dir (%s/seed/azure)' % self.tmp, dsrc.subplatform)
+
+ def test_basic_dev_file(self):
+ """When a device path is used, present that in subplatform."""
+ data = {'sys_cfg': {}, 'dsdevs': ['/dev/cd0']}
+ dsrc = self._get_ds(data)
+ with mock.patch(MOCKPATH + 'util.mount_cb') as m_mount_cb:
+ m_mount_cb.return_value = (
+ {'local-hostname': 'me'}, 'ud', {'cfg': ''}, {})
+ self.assertTrue(dsrc.get_data())
+ self.assertEqual(dsrc.userdata_raw, 'ud')
+ self.assertEqual(dsrc.metadata['local-hostname'], 'me')
+ self.assertEqual('azure', dsrc.cloud_name)
+ self.assertEqual('azure', dsrc.platform_type)
+ self.assertEqual('config-disk (/dev/cd0)', dsrc.subplatform)
def test_get_data_non_ubuntu_will_not_remove_network_scripts(self):
"""get_data on non-Ubuntu will not remove ubuntu net scripts."""
@@ -769,8 +789,8 @@ fdescfs /dev/fd fdescfs rw 0 0
ds.get_data()
self.assertEqual(self.instance_id, ds.metadata['instance-id'])
- @mock.patch("cloudinit.sources.DataSourceAzure.util.is_FreeBSD")
- @mock.patch("cloudinit.sources.DataSourceAzure._check_freebsd_cdrom")
+ @mock.patch(MOCKPATH + 'util.is_FreeBSD')
+ @mock.patch(MOCKPATH + '_check_freebsd_cdrom')
def test_list_possible_azure_ds_devs(self, m_check_fbsd_cdrom,
m_is_FreeBSD):
"""On FreeBSD, possible devs should show /dev/cd0."""
@@ -885,17 +905,17 @@ fdescfs /dev/fd fdescfs rw 0 0
expected_config['config'].append(blacklist_config)
self.assertEqual(netconfig, expected_config)
- @mock.patch("cloudinit.sources.DataSourceAzure.util.subp")
+ @mock.patch(MOCKPATH + 'util.subp')
def test_get_hostname_with_no_args(self, subp):
dsaz.get_hostname()
subp.assert_called_once_with(("hostname",), capture=True)
- @mock.patch("cloudinit.sources.DataSourceAzure.util.subp")
+ @mock.patch(MOCKPATH + 'util.subp')
def test_get_hostname_with_string_arg(self, subp):
dsaz.get_hostname(hostname_command="hostname")
subp.assert_called_once_with(("hostname",), capture=True)
- @mock.patch("cloudinit.sources.DataSourceAzure.util.subp")
+ @mock.patch(MOCKPATH + 'util.subp')
def test_get_hostname_with_iterable_arg(self, subp):
dsaz.get_hostname(hostname_command=("hostname",))
subp.assert_called_once_with(("hostname",), capture=True)
@@ -949,7 +969,7 @@ class TestAzureBounce(CiTestCase):
self.set_hostname = self.patches.enter_context(
mock.patch.object(dsaz, 'set_hostname'))
self.subp = self.patches.enter_context(
- mock.patch('cloudinit.sources.DataSourceAzure.util.subp'))
+ mock.patch(MOCKPATH + 'util.subp'))
self.find_fallback_nic = self.patches.enter_context(
mock.patch('cloudinit.net.find_fallback_nic', return_value='eth9'))
@@ -989,7 +1009,7 @@ class TestAzureBounce(CiTestCase):
ds.get_data()
self.assertEqual(0, self.set_hostname.call_count)
- @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
def test_disabled_bounce_does_not_perform_bounce(
self, perform_hostname_bounce):
cfg = {'hostname_bounce': {'policy': 'off'}}
@@ -1005,7 +1025,7 @@ class TestAzureBounce(CiTestCase):
ds.get_data()
self.assertEqual(0, self.set_hostname.call_count)
- @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
def test_unchanged_hostname_does_not_perform_bounce(
self, perform_hostname_bounce):
host_name = 'unchanged-host-name'
@@ -1015,7 +1035,7 @@ class TestAzureBounce(CiTestCase):
ds.get_data()
self.assertEqual(0, perform_hostname_bounce.call_count)
- @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
def test_force_performs_bounce_regardless(self, perform_hostname_bounce):
host_name = 'unchanged-host-name'
self.get_hostname.return_value = host_name
@@ -1032,7 +1052,7 @@ class TestAzureBounce(CiTestCase):
cfg = {'hostname_bounce': {'policy': 'force'}}
dsrc = self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg),
agent_command=['not', '__builtin__'])
- patch_path = 'cloudinit.sources.DataSourceAzure.util.which'
+ patch_path = MOCKPATH + 'util.which'
with mock.patch(patch_path) as m_which:
m_which.return_value = None
ret = self._get_and_setup(dsrc)
@@ -1053,7 +1073,7 @@ class TestAzureBounce(CiTestCase):
self.assertEqual(expected_hostname,
self.set_hostname.call_args_list[0][0][0])
- @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
def test_different_hostnames_performs_bounce(
self, perform_hostname_bounce):
expected_hostname = 'azure-expected-host-name'
@@ -1076,7 +1096,7 @@ class TestAzureBounce(CiTestCase):
self.assertEqual(initial_host_name,
self.set_hostname.call_args_list[-1][0][0])
- @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
def test_failure_in_bounce_still_resets_host_name(
self, perform_hostname_bounce):
perform_hostname_bounce.side_effect = Exception
@@ -1117,7 +1137,7 @@ class TestAzureBounce(CiTestCase):
self.assertEqual(
dsaz.BOUNCE_COMMAND_IFUP, bounce_args)
- @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
def test_set_hostname_option_can_disable_bounce(
self, perform_hostname_bounce):
cfg = {'set_hostname': False, 'hostname_bounce': {'policy': 'force'}}
@@ -1218,12 +1238,12 @@ class TestCanDevBeReformatted(CiTestCase):
def has_ntfs_fs(device):
return bypath.get(device, {}).get('fs') == 'ntfs'
- p = 'cloudinit.sources.DataSourceAzure'
- self._domock(p + "._partitions_on_device", 'm_partitions_on_device')
- self._domock(p + "._has_ntfs_filesystem", 'm_has_ntfs_filesystem')
- self._domock(p + ".util.mount_cb", 'm_mount_cb')
- self._domock(p + ".os.path.realpath", 'm_realpath')
- self._domock(p + ".os.path.exists", 'm_exists')
+ p = MOCKPATH
+ self._domock(p + "_partitions_on_device", 'm_partitions_on_device')
+ self._domock(p + "_has_ntfs_filesystem", 'm_has_ntfs_filesystem')
+ self._domock(p + "util.mount_cb", 'm_mount_cb')
+ self._domock(p + "os.path.realpath", 'm_realpath')
+ self._domock(p + "os.path.exists", 'm_exists')
self.m_exists.side_effect = lambda p: p in bypath
self.m_realpath.side_effect = realpath
@@ -1488,7 +1508,7 @@ class TestPreprovisioningShouldReprovision(CiTestCase):
self.paths = helpers.Paths({'cloud_dir': tmp})
dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
- @mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
+ @mock.patch(MOCKPATH + 'util.write_file')
def test__should_reprovision_with_true_cfg(self, isfile, write_f):
"""The _should_reprovision method should return true with config
flag present."""
@@ -1512,7 +1532,7 @@ class TestPreprovisioningShouldReprovision(CiTestCase):
dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
self.assertFalse(dsa._should_reprovision((None, None, {}, None)))
- @mock.patch('cloudinit.sources.DataSourceAzure.DataSourceAzure._poll_imds')
+ @mock.patch(MOCKPATH + 'DataSourceAzure._poll_imds')
def test_reprovision_calls__poll_imds(self, _poll_imds, isfile):
"""_reprovision will poll IMDS."""
isfile.return_value = False
@@ -1528,8 +1548,7 @@ class TestPreprovisioningShouldReprovision(CiTestCase):
@mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
@mock.patch('requests.Session.request')
-@mock.patch(
- 'cloudinit.sources.DataSourceAzure.DataSourceAzure._report_ready')
+@mock.patch(MOCKPATH + 'DataSourceAzure._report_ready')
class TestPreprovisioningPollIMDS(CiTestCase):
def setUp(self):
@@ -1539,7 +1558,7 @@ class TestPreprovisioningPollIMDS(CiTestCase):
self.paths = helpers.Paths({'cloud_dir': self.tmp})
dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
- @mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
+ @mock.patch(MOCKPATH + 'util.write_file')
def test_poll_imds_calls_report_ready(self, write_f, report_ready_func,
fake_resp, m_dhcp, m_net):
"""The poll_imds will call report_ready after creating marker file."""
@@ -1550,8 +1569,7 @@ class TestPreprovisioningPollIMDS(CiTestCase):
'unknown-245': '624c3620'}
m_dhcp.return_value = [lease]
dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
- mock_path = (
- 'cloudinit.sources.DataSourceAzure.REPORTED_READY_MARKER_FILE')
+ mock_path = (MOCKPATH + 'REPORTED_READY_MARKER_FILE')
with mock.patch(mock_path, report_marker):
dsa._poll_imds()
self.assertEqual(report_ready_func.call_count, 1)
@@ -1561,23 +1579,21 @@ class TestPreprovisioningPollIMDS(CiTestCase):
fake_resp, m_dhcp, m_net):
"""The poll_imds should not call reporting ready
when flag is false"""
- report_marker = self.tmp_path('report_marker', self.tmp)
- write_file(report_marker, content='dont run report_ready :)')
+ report_file = self.tmp_path('report_marker', self.tmp)
+ write_file(report_file, content='dont run report_ready :)')
m_dhcp.return_value = [{
'interface': 'eth9', 'fixed-address': '192.168.2.9',
'routers': '192.168.2.1', 'subnet-mask': '255.255.255.0',
'unknown-245': '624c3620'}]
dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
- mock_path = (
- 'cloudinit.sources.DataSourceAzure.REPORTED_READY_MARKER_FILE')
- with mock.patch(mock_path, report_marker):
+ with mock.patch(MOCKPATH + 'REPORTED_READY_MARKER_FILE', report_file):
dsa._poll_imds()
self.assertEqual(report_ready_func.call_count, 0)
-@mock.patch('cloudinit.sources.DataSourceAzure.util.subp')
-@mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
-@mock.patch('cloudinit.sources.DataSourceAzure.util.is_FreeBSD')
+@mock.patch(MOCKPATH + 'util.subp')
+@mock.patch(MOCKPATH + 'util.write_file')
+@mock.patch(MOCKPATH + 'util.is_FreeBSD')
@mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
@mock.patch('requests.Session.request')
@@ -1688,7 +1704,7 @@ class TestRemoveUbuntuNetworkConfigScripts(CiTestCase):
self.tmp_path('notfilehere', dir=self.tmp)])
self.assertNotIn('/not/a', self.logs.getvalue()) # No delete logs
- @mock.patch('cloudinit.sources.DataSourceAzure.os.path.exists')
+ @mock.patch(MOCKPATH + 'os.path.exists')
def test_remove_network_scripts_default_removes_stock_scripts(self,
m_exists):
"""Azure's stock ubuntu image scripts and artifacts are removed."""
@@ -1704,14 +1720,14 @@ class TestWBIsPlatformViable(CiTestCase):
"""White box tests for _is_platform_viable."""
with_logs = True
- @mock.patch('cloudinit.sources.DataSourceAzure.util.read_dmi_data')
+ @mock.patch(MOCKPATH + 'util.read_dmi_data')
def test_true_on_non_azure_chassis(self, m_read_dmi_data):
"""Return True if DMI chassis-asset-tag is AZURE_CHASSIS_ASSET_TAG."""
m_read_dmi_data.return_value = dsaz.AZURE_CHASSIS_ASSET_TAG
self.assertTrue(dsaz._is_platform_viable('doesnotmatter'))
- @mock.patch('cloudinit.sources.DataSourceAzure.os.path.exists')
- @mock.patch('cloudinit.sources.DataSourceAzure.util.read_dmi_data')
+ @mock.patch(MOCKPATH + 'os.path.exists')
+ @mock.patch(MOCKPATH + 'util.read_dmi_data')
def test_true_on_azure_ovf_env_in_seed_dir(self, m_read_dmi_data, m_exist):
"""Return True if ovf-env.xml exists in known seed dirs."""
# Non-matching Azure chassis-asset-tag
@@ -1729,7 +1745,7 @@ class TestWBIsPlatformViable(CiTestCase):
and no devices have a label starting with prefix 'rd_rdfe_'.
"""
self.assertFalse(wrap_and_call(
- 'cloudinit.sources.DataSourceAzure',
+ MOCKPATH,
{'os.path.exists': False,
# Non-matching Azure chassis-asset-tag
'util.read_dmi_data': dsaz.AZURE_CHASSIS_ASSET_TAG + 'X',
diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py
index 380ad1b5..3bf52e69 100644
--- a/tests/unittests/test_datasource/test_cloudsigma.py
+++ b/tests/unittests/test_datasource/test_cloudsigma.py
@@ -68,6 +68,12 @@ class DataSourceCloudSigmaTest(test_helpers.CiTestCase):
self.assertEqual(SERVER_CONTEXT['uuid'],
self.datasource.get_instance_id())
+ def test_platform(self):
+ """All platform-related attributes are set."""
+ self.assertEqual(self.datasource.cloud_name, 'cloudsigma')
+ self.assertEqual(self.datasource.platform_type, 'cloudsigma')
+ self.assertEqual(self.datasource.subplatform, 'cepko (/dev/ttyS1)')
+
def test_metadata(self):
self.assertEqual(self.datasource.metadata, SERVER_CONTEXT)
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 231619c9..dcdabea5 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -478,6 +478,9 @@ class TestConfigDriveDataSource(CiTestCase):
myds = cfg_ds_from_dir(self.tmp, files=CFG_DRIVE_FILES_V2)
self.assertEqual(myds.get_public_ssh_keys(),
[OSTACK_META['public_keys']['mykey']])
+ self.assertEqual('configdrive', myds.cloud_name)
+ self.assertEqual('openstack', myds.platform)
+ self.assertEqual('seed-dir (%s/seed)' % self.tmp, myds.subplatform)
class TestNetJson(CiTestCase):
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index 497e7610..9f81255a 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -351,7 +351,9 @@ class TestEc2(test_helpers.HttprettyTestCase):
m_get_interface_mac.return_value = mac1
nc = ds.network_config # Will re-crawl network metadata
self.assertIsNotNone(nc)
- self.assertIn('Re-crawl of metadata service', self.logs.getvalue())
+ self.assertIn(
+ 'Refreshing stale metadata from prior to upgrade',
+ self.logs.getvalue())
expected = {'version': 1, 'config': [
{'mac_address': '06:17:04:d7:26:09',
'name': 'eth9',
@@ -386,7 +388,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
register_mock_metaserver(
'{0}/{1}/dynamic/'.format(ds.metadata_address, all_versions[-1]),
DYNAMIC_METADATA)
- ds._cloud_platform = ec2.Platforms.AWS
+ ds._cloud_name = ec2.CloudNames.AWS
# Setup cached metadata on the Datasource
ds.metadata = DEFAULT_METADATA
self.assertEqual('my-identity-id', ds.get_instance_id())
@@ -401,6 +403,9 @@ class TestEc2(test_helpers.HttprettyTestCase):
ret = ds.get_data()
self.assertTrue(ret)
self.assertEqual(0, m_dhcp.call_count)
+ self.assertEqual('aws', ds.cloud_name)
+ self.assertEqual('ec2', ds.platform_type)
+ self.assertEqual('metadata (%s)' % ds.metadata_address, ds.subplatform)
def test_valid_platform_with_strict_false(self):
"""Valid platform data should return true with strict_id false."""
@@ -439,16 +444,17 @@ class TestEc2(test_helpers.HttprettyTestCase):
sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
md=DEFAULT_METADATA)
platform_attrs = [
- attr for attr in ec2.Platforms.__dict__.keys()
+ attr for attr in ec2.CloudNames.__dict__.keys()
if not attr.startswith('__')]
for attr_name in platform_attrs:
- platform_name = getattr(ec2.Platforms, attr_name)
- if platform_name != 'AWS':
- ds._cloud_platform = platform_name
+ platform_name = getattr(ec2.CloudNames, attr_name)
+ if platform_name != 'aws':
+ ds._cloud_name = platform_name
ret = ds.get_data()
+ self.assertEqual('ec2', ds.platform_type)
self.assertFalse(ret)
message = (
- "Local Ec2 mode only supported on ('AWS',),"
+ "Local Ec2 mode only supported on ('aws',),"
' not {0}'.format(platform_name))
self.assertIn(message, self.logs.getvalue())
diff --git a/tests/unittests/test_datasource/test_ibmcloud.py b/tests/unittests/test_datasource/test_ibmcloud.py
index e639ae47..0b54f585 100644
--- a/tests/unittests/test_datasource/test_ibmcloud.py
+++ b/tests/unittests/test_datasource/test_ibmcloud.py
@@ -1,14 +1,17 @@
# This file is part of cloud-init. See LICENSE file for license information.
+from cloudinit.helpers import Paths
from cloudinit.sources import DataSourceIBMCloud as ibm
from cloudinit.tests import helpers as test_helpers
+from cloudinit import util
import base64
import copy
import json
-import mock
from textwrap import dedent
+mock = test_helpers.mock
+
D_PATH = "cloudinit.sources.DataSourceIBMCloud."
@@ -309,4 +312,39 @@ class TestIsIBMProvisioning(test_helpers.FilesystemMockingTestCase):
self.assertIn("no reference file", self.logs.getvalue())
+class TestDataSourceIBMCloud(test_helpers.CiTestCase):
+
+ def setUp(self):
+ super(TestDataSourceIBMCloud, self).setUp()
+ self.tmp = self.tmp_dir()
+ self.cloud_dir = self.tmp_path('cloud', dir=self.tmp)
+ util.ensure_dir(self.cloud_dir)
+ paths = Paths({'run_dir': self.tmp, 'cloud_dir': self.cloud_dir})
+ self.ds = ibm.DataSourceIBMCloud(
+ sys_cfg={}, distro=None, paths=paths)
+
+ def test_get_data_false(self):
+ """When read_md returns None, get_data returns False."""
+ with mock.patch(D_PATH + 'read_md', return_value=None):
+ self.assertFalse(self.ds.get_data())
+
+ def test_get_data_processes_read_md(self):
+ """get_data processes and caches content returned by read_md."""
+ md = {
+ 'metadata': {}, 'networkdata': 'net', 'platform': 'plat',
+ 'source': 'src', 'system-uuid': 'uuid', 'userdata': 'ud',
+ 'vendordata': 'vd'}
+ with mock.patch(D_PATH + 'read_md', return_value=md):
+ self.assertTrue(self.ds.get_data())
+ self.assertEqual('src', self.ds.source)
+ self.assertEqual('plat', self.ds.platform)
+ self.assertEqual({}, self.ds.metadata)
+ self.assertEqual('ud', self.ds.userdata_raw)
+ self.assertEqual('net', self.ds.network_json)
+ self.assertEqual('vd', self.ds.vendordata_pure)
+ self.assertEqual('uuid', self.ds.system_uuid)
+ self.assertEqual('ibmcloud', self.ds.cloud_name)
+ self.assertEqual('ibmcloud', self.ds.platform_type)
+ self.assertEqual('plat (src)', self.ds.subplatform)
+
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py
index 21931eb7..b6468b6d 100644
--- a/tests/unittests/test_datasource/test_nocloud.py
+++ b/tests/unittests/test_datasource/test_nocloud.py
@@ -10,6 +10,7 @@ import textwrap
import yaml
+@mock.patch('cloudinit.sources.DataSourceNoCloud.util.is_lxd')
class TestNoCloudDataSource(CiTestCase):
def setUp(self):
@@ -28,10 +29,11 @@ class TestNoCloudDataSource(CiTestCase):
self.mocks.enter_context(
mock.patch.object(util, 'read_dmi_data', return_value=None))
- def test_nocloud_seed_dir(self):
+ def test_nocloud_seed_dir_on_lxd(self, m_is_lxd):
md = {'instance-id': 'IID', 'dsmode': 'local'}
ud = b"USER_DATA_HERE"
- populate_dir(os.path.join(self.paths.seed_dir, "nocloud"),
+ seed_dir = os.path.join(self.paths.seed_dir, "nocloud")
+ populate_dir(seed_dir,
{'user-data': ud, 'meta-data': yaml.safe_dump(md)})
sys_cfg = {
@@ -44,9 +46,32 @@ class TestNoCloudDataSource(CiTestCase):
ret = dsrc.get_data()
self.assertEqual(dsrc.userdata_raw, ud)
self.assertEqual(dsrc.metadata, md)
+ self.assertEqual(dsrc.platform_type, 'lxd')
+ self.assertEqual(
+ dsrc.subplatform, 'seed-dir (%s)' % seed_dir)
self.assertTrue(ret)
- def test_fs_label(self):
+ def test_nocloud_seed_dir_non_lxd_platform_is_nocloud(self, m_is_lxd):
+ """Non-lxd environments will list nocloud as the platform."""
+ m_is_lxd.return_value = False
+ md = {'instance-id': 'IID', 'dsmode': 'local'}
+ seed_dir = os.path.join(self.paths.seed_dir, "nocloud")
+ populate_dir(seed_dir,
+ {'user-data': '', 'meta-data': yaml.safe_dump(md)})
+
+ sys_cfg = {
+ 'datasource': {'NoCloud': {'fs_label': None}}
+ }
+
+ ds = DataSourceNoCloud.DataSourceNoCloud
+
+ dsrc = ds(sys_cfg=sys_cfg, distro=None, paths=self.paths)
+ self.assertTrue(dsrc.get_data())
+ self.assertEqual(dsrc.platform_type, 'nocloud')
+ self.assertEqual(
+ dsrc.subplatform, 'seed-dir (%s)' % seed_dir)
+
+ def test_fs_label(self, m_is_lxd):
# find_devs_with should not be called ff fs_label is None
ds = DataSourceNoCloud.DataSourceNoCloud
@@ -68,7 +93,7 @@ class TestNoCloudDataSource(CiTestCase):
ret = dsrc.get_data()
self.assertFalse(ret)
- def test_no_datasource_expected(self):
+ def test_no_datasource_expected(self, m_is_lxd):
# no source should be found if no cmdline, config, and fs_label=None
sys_cfg = {'datasource': {'NoCloud': {'fs_label': None}}}
@@ -76,7 +101,7 @@ class TestNoCloudDataSource(CiTestCase):
dsrc = ds(sys_cfg=sys_cfg, distro=None, paths=self.paths)
self.assertFalse(dsrc.get_data())
- def test_seed_in_config(self):
+ def test_seed_in_config(self, m_is_lxd):
ds = DataSourceNoCloud.DataSourceNoCloud
data = {
@@ -92,7 +117,7 @@ class TestNoCloudDataSource(CiTestCase):
self.assertEqual(dsrc.metadata.get('instance-id'), 'IID')
self.assertTrue(ret)
- def test_nocloud_seed_with_vendordata(self):
+ def test_nocloud_seed_with_vendordata(self, m_is_lxd):
md = {'instance-id': 'IID', 'dsmode': 'local'}
ud = b"USER_DATA_HERE"
vd = b"THIS IS MY VENDOR_DATA"
@@ -114,7 +139,7 @@ class TestNoCloudDataSource(CiTestCase):
self.assertEqual(dsrc.vendordata_raw, vd)
self.assertTrue(ret)
- def test_nocloud_no_vendordata(self):
+ def test_nocloud_no_vendordata(self, m_is_lxd):
populate_dir(os.path.join(self.paths.seed_dir, "nocloud"),
{'user-data': b"ud", 'meta-data': "instance-id: IID\n"})
@@ -128,7 +153,7 @@ class TestNoCloudDataSource(CiTestCase):
self.assertFalse(dsrc.vendordata)
self.assertTrue(ret)
- def test_metadata_network_interfaces(self):
+ def test_metadata_network_interfaces(self, m_is_lxd):
gateway = "103.225.10.1"
md = {
'instance-id': 'i-abcd',
@@ -157,7 +182,7 @@ class TestNoCloudDataSource(CiTestCase):
# very simple check just for the strings above
self.assertIn(gateway, str(dsrc.network_config))
- def test_metadata_network_config(self):
+ def test_metadata_network_config(self, m_is_lxd):
# network-config needs to get into network_config
netconf = {'version': 1,
'config': [{'type': 'physical', 'name': 'interface0',
@@ -177,7 +202,7 @@ class TestNoCloudDataSource(CiTestCase):
self.assertTrue(ret)
self.assertEqual(netconf, dsrc.network_config)
- def test_metadata_network_config_over_interfaces(self):
+ def test_metadata_network_config_over_interfaces(self, m_is_lxd):
# network-config should override meta-data/network-interfaces
gateway = "103.225.10.1"
md = {
diff --git a/tests/unittests/test_datasource/test_opennebula.py b/tests/unittests/test_datasource/test_opennebula.py
index 61591017..bb399f6d 100644
--- a/tests/unittests/test_datasource/test_opennebula.py
+++ b/tests/unittests/test_datasource/test_opennebula.py
@@ -123,6 +123,10 @@ class TestOpenNebulaDataSource(CiTestCase):
self.assertTrue(ret)
finally:
util.find_devs_with = orig_find_devs_with
+ self.assertEqual('opennebula', dsrc.cloud_name)
+ self.assertEqual('opennebula', dsrc.platform_type)
+ self.assertEqual(
+ 'seed-dir (%s/seed/opennebula)' % self.tmp, dsrc.subplatform)
def test_seed_dir_non_contextdisk(self):
self.assertRaises(ds.NonContextDiskDir, ds.read_context_disk_dir,
diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py
index 9d52eb99..a226c032 100644
--- a/tests/unittests/test_datasource/test_ovf.py
+++ b/tests/unittests/test_datasource/test_ovf.py
@@ -11,7 +11,7 @@ from collections import OrderedDict
from textwrap import dedent
from cloudinit import util
-from cloudinit.tests.helpers import CiTestCase, wrap_and_call
+from cloudinit.tests.helpers import CiTestCase, mock, wrap_and_call
from cloudinit.helpers import Paths
from cloudinit.sources import DataSourceOVF as dsovf
from cloudinit.sources.helpers.vmware.imc.config_custom_script import (
@@ -120,7 +120,7 @@ class TestDatasourceOVF(CiTestCase):
def test_get_data_false_on_none_dmi_data(self):
"""When dmi for system-product-name is None, get_data returns False."""
- paths = Paths({'seed_dir': self.tdir})
+ paths = Paths({'cloud_dir': self.tdir})
ds = self.datasource(sys_cfg={}, distro={}, paths=paths)
retcode = wrap_and_call(
'cloudinit.sources.DataSourceOVF',
@@ -134,7 +134,7 @@ class TestDatasourceOVF(CiTestCase):
def test_get_data_no_vmware_customization_disabled(self):
"""When vmware customization is disabled via sys_cfg log a message."""
- paths = Paths({'seed_dir': self.tdir})
+ paths = Paths({'cloud_dir': self.tdir})
ds = self.datasource(
sys_cfg={'disable_vmware_customization': True}, distro={},
paths=paths)
@@ -153,7 +153,7 @@ class TestDatasourceOVF(CiTestCase):
"""When cloud-init workflow for vmware is enabled via sys_cfg log a
message.
"""
- paths = Paths({'seed_dir': self.tdir})
+ paths = Paths({'cloud_dir': self.tdir})
ds = self.datasource(
sys_cfg={'disable_vmware_customization': False}, distro={},
paths=paths)
@@ -178,6 +178,50 @@ class TestDatasourceOVF(CiTestCase):
self.assertIn('Script %s not found!!' % customscript,
str(context.exception))
+ def test_get_data_non_vmware_seed_platform_info(self):
+ """Platform info properly reports when on non-vmware platforms."""
+ paths = Paths({'cloud_dir': self.tdir, 'run_dir': self.tdir})
+ # Write ovf-env.xml seed file
+ seed_dir = self.tmp_path('seed', dir=self.tdir)
+ ovf_env = self.tmp_path('ovf-env.xml', dir=seed_dir)
+ util.write_file(ovf_env, OVF_ENV_CONTENT)
+ ds = self.datasource(sys_cfg={}, distro={}, paths=paths)
+
+ self.assertEqual('ovf', ds.cloud_name)
+ self.assertEqual('ovf', ds.platform_type)
+ MPATH = 'cloudinit.sources.DataSourceOVF.'
+ with mock.patch(MPATH + 'util.read_dmi_data', return_value='!VMware'):
+ with mock.patch(MPATH + 'transport_vmware_guestd') as m_guestd:
+ with mock.patch(MPATH + 'transport_iso9660') as m_iso9660:
+ m_iso9660.return_value = (None, 'ignored', 'ignored')
+ m_guestd.return_value = (None, 'ignored', 'ignored')
+ self.assertTrue(ds.get_data())
+ self.assertEqual(
+ 'ovf (%s/seed/ovf-env.xml)' % self.tdir,
+ ds.subplatform)
+
+ def test_get_data_vmware_seed_platform_info(self):
+ """Platform info properly reports when on VMware platform."""
+ paths = Paths({'cloud_dir': self.tdir, 'run_dir': self.tdir})
+ # Write ovf-env.xml seed file
+ seed_dir = self.tmp_path('seed', dir=self.tdir)
+ ovf_env = self.tmp_path('ovf-env.xml', dir=seed_dir)
+ util.write_file(ovf_env, OVF_ENV_CONTENT)
+ ds = self.datasource(sys_cfg={}, distro={}, paths=paths)
+
+ self.assertEqual('ovf', ds.cloud_name)
+ self.assertEqual('ovf', ds.platform_type)
+ MPATH = 'cloudinit.sources.DataSourceOVF.'
+ with mock.patch(MPATH + 'util.read_dmi_data', return_value='VMWare'):
+ with mock.patch(MPATH + 'transport_vmware_guestd') as m_guestd:
+ with mock.patch(MPATH + 'transport_iso9660') as m_iso9660:
+ m_iso9660.return_value = (None, 'ignored', 'ignored')
+ m_guestd.return_value = (None, 'ignored', 'ignored')
+ self.assertTrue(ds.get_data())
+ self.assertEqual(
+ 'vmware (%s/seed/ovf-env.xml)' % self.tdir,
+ ds.subplatform)
+
class TestTransportIso9660(CiTestCase):
diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py
index 46d67b94..42ac6971 100644
--- a/tests/unittests/test_datasource/test_smartos.py
+++ b/tests/unittests/test_datasource/test_smartos.py
@@ -426,6 +426,13 @@ class TestSmartOSDataSource(FilesystemMockingTestCase):
self.assertEqual(MOCK_RETURNS['sdc:uuid'],
dsrc.metadata['instance-id'])
+ def test_platform_info(self):
+ """All platform-related attributes are properly set."""
+ dsrc = self._get_ds(mockdata=MOCK_RETURNS)
+ self.assertEqual('joyent', dsrc.cloud_name)
+ self.assertEqual('joyent', dsrc.platform_type)
+ self.assertEqual('serial (/dev/ttyS1)', dsrc.subplatform)
+
def test_root_keys(self):
dsrc = self._get_ds(mockdata=MOCK_RETURNS)
ret = dsrc.get_data()