diff options
Diffstat (limited to 'cloudinit/sources/DataSourceSmartOS.py')
-rw-r--r-- | cloudinit/sources/DataSourceSmartOS.py | 133 |
1 files changed, 113 insertions, 20 deletions
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py index 1ce20c10..551b20c4 100644 --- a/cloudinit/sources/DataSourceSmartOS.py +++ b/cloudinit/sources/DataSourceSmartOS.py @@ -27,6 +27,7 @@ # +import base64 from cloudinit import log as logging from cloudinit import sources from cloudinit import util @@ -35,8 +36,6 @@ import os.path import serial -DEF_TTY_LOC = '/dev/ttyS1' -DEF_TTY_TIMEOUT = 60 LOG = logging.getLogger(__name__) SMARTOS_ATTRIB_MAP = { @@ -47,17 +46,66 @@ SMARTOS_ATTRIB_MAP = { 'user-data': ('user-data', False), 'iptables_disable': ('iptables_disable', True), 'motd_sys_info': ('motd_sys_info', True), + 'availability_zone': ('datacenter_name', True), +} + +DS_NAME = 'SmartOS' +DS_CFG_PATH = ['datasource', DS_NAME] +# BUILT-IN DATASOURCE CONFIGURATION +# The following is the built-in configuration. If the values +# are not set via the system configuration, then these default +# will be used: +# serial_device: which serial device to use for the meta-data +# seed_timeout: how long to wait on the device +# no_base64_decode: values which are not base64 encoded and +# are fetched directly from SmartOS, not meta-data values +# base64_keys: meta-data keys that are delivered in base64 +# base64_all: with the exclusion of no_base64_decode values, +# treat all meta-data as base64 encoded +# disk_setup: describes how to partition the ephemeral drive +# fs_setup: describes how to format the ephemeral drive +# +BUILTIN_DS_CONFIG = { + 'serial_device': '/dev/ttyS1', + 'seed_timeout': 60, + 'no_base64_decode': ['root_authorized_keys', + 'motd_sys_info', + 'iptables_disable'], + 'base64_keys': [], + 'base64_all': False, + 'disk_aliases': {'ephemeral0': '/dev/vdb'}, +} + +BUILTIN_CLOUD_CONFIG = { + 'disk_setup': { + 'ephemeral0': {'table_type': 'mbr', + 'layout': False, + 'overwrite': False} + }, + 'fs_setup': [{'label': 'ephemeral0', + 'filesystem': 'ext3', + 'device': 'ephemeral0'}], } class DataSourceSmartOS(sources.DataSource): def __init__(self, sys_cfg, distro, paths): sources.DataSource.__init__(self, sys_cfg, distro, paths) - self.seed_dir = os.path.join(paths.seed_dir, 'sdc') self.is_smartdc = None - self.seed = self.sys_cfg.get("serial_device", DEF_TTY_LOC) - self.seed_timeout = self.sys_cfg.get("serial_timeout", - DEF_TTY_TIMEOUT) + + self.ds_cfg = util.mergemanydict([ + self.ds_cfg, + util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}), + BUILTIN_DS_CONFIG]) + + self.metadata = {} + self.cfg = BUILTIN_CLOUD_CONFIG + + self.seed = self.ds_cfg.get("serial_device") + self.seed_timeout = self.ds_cfg.get("serial_timeout") + self.smartos_no_base64 = self.ds_cfg.get('no_base64_decode') + self.b64_keys = self.ds_cfg.get('base64_keys') + self.b64_all = self.ds_cfg.get('base64_all') def __str__(self): root = sources.DataSource.__str__(self) @@ -70,7 +118,6 @@ class DataSourceSmartOS(sources.DataSource): if not os.path.exists(self.seed): LOG.debug("Host does not appear to be on SmartOS") return False - self.seed = self.seed dmi_info = dmi_data() if dmi_info is False: @@ -79,35 +126,60 @@ class DataSourceSmartOS(sources.DataSource): system_uuid, system_type = dmi_info if 'smartdc' not in system_type.lower(): - LOG.debug("Host is not on SmartOS") + LOG.debug("Host is not on SmartOS. system_type=%s", system_type) return False self.is_smartdc = True md['instance-id'] = system_uuid + b64_keys = self.query('base64_keys', strip=True, b64=False) + if b64_keys is not None: + self.b64_keys = [k.strip() for k in str(b64_keys).split(',')] + + b64_all = self.query('base64_all', strip=True, b64=False) + if b64_all is not None: + self.b64_all = util.is_true(b64_all) + for ci_noun, attribute in SMARTOS_ATTRIB_MAP.iteritems(): smartos_noun, strip = attribute - md[ci_noun] = query_data(smartos_noun, self.seed, - self.seed_timeout, strip=strip) + md[ci_noun] = self.query(smartos_noun, strip=strip) if not md['local-hostname']: md['local-hostname'] = system_uuid + ud = None if md['user-data']: ud = md['user-data'] - else: + elif md['user-script']: ud = md['user-script'] - self.metadata = md + self.metadata = util.mergemanydict([md, self.metadata]) self.userdata_raw = ud return True + def device_name_to_device(self, name): + return self.ds_cfg['disk_aliases'].get(name) + + def get_config_obj(self): + return self.cfg + def get_instance_id(self): return self.metadata['instance-id'] + def query(self, noun, strip=False, default=None, b64=None): + if b64 is None: + if noun in self.smartos_no_base64: + b64 = False + elif self.b64_all or noun in self.b64_keys: + b64 = True + + return query_data(noun=noun, strip=strip, seed_device=self.seed, + seed_timeout=self.seed_timeout, default=default, + b64=b64) + def get_serial(seed_device, seed_timeout): """This is replaced in unit testing, allowing us to replace - serial.Serial with a mocked class + serial.Serial with a mocked class. The timeout value of 60 seconds should never be hit. The value is taken from SmartOS own provisioning tools. Since we are reading @@ -124,12 +196,18 @@ def get_serial(seed_device, seed_timeout): return ser -def query_data(noun, seed_device, seed_timeout, strip=False): +def query_data(noun, seed_device, seed_timeout, strip=False, default=None, + b64=None): """Makes a request to via the serial console via "GET <NOUN>" In the response, the first line is the status, while subsequent lines are is the value. A blank line with a "." is used to indicate end of response. + + If the response is expected to be base64 encoded, then set b64encoded + to true. Unfortantely, there is no way to know if something is 100% + encoded, so this method relies on being told if the data is base64 or + not. """ if not noun: @@ -143,7 +221,7 @@ def query_data(noun, seed_device, seed_timeout, strip=False): if 'SUCCESS' not in status: ser.close() - return None + return default while not eom_found: m = ser.readline() @@ -153,12 +231,27 @@ def query_data(noun, seed_device, seed_timeout, strip=False): response.append(m) ser.close() - if not strip: - return "".join(response) + + if b64 is None: + b64 = query_data('b64-%s' % noun, seed_device=seed_device, + seed_timeout=seed_timeout, b64=False, + default=False, strip=True) + b64 = util.is_true(b64) + + resp = None + if b64 or strip: + resp = "".join(response).rstrip() else: - return "".join(response).rstrip() + resp = "".join(response) + + if b64: + try: + return base64.b64decode(resp) + except TypeError: + LOG.warn("Failed base64 decoding key '%s'", noun) + return resp - return None + return resp def dmi_data(): @@ -181,7 +274,7 @@ def dmi_data(): except Exception as e: util.logexc(LOG, "Failed to get system UUID", e) - return sys_uuid.lower(), sys_type + return (sys_uuid.lower().strip(), sys_type.strip()) # Used to match classes to dependencies |