diff options
Diffstat (limited to 'cloudinit/sources/DataSourceSmartOS.py')
| -rw-r--r-- | cloudinit/sources/DataSourceSmartOS.py | 555 | 
1 files changed, 313 insertions, 242 deletions
| diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py index 9b16bf8d..40f915fa 100644 --- a/cloudinit/sources/DataSourceSmartOS.py +++ b/cloudinit/sources/DataSourceSmartOS.py @@ -32,55 +32,51 @@ import socket  from cloudinit import dmi  from cloudinit import log as logging -from cloudinit import serial -from cloudinit import sources -from cloudinit import subp -from cloudinit import util +from cloudinit import serial, sources, subp, util  from cloudinit.event import EventScope, EventType  LOG = logging.getLogger(__name__)  SMARTOS_ATTRIB_MAP = {      # Cloud-init Key : (SmartOS Key, Strip line endings) -    'instance-id': ('sdc:uuid', True), -    'local-hostname': ('hostname', True), -    'public-keys': ('root_authorized_keys', True), -    'user-script': ('user-script', False), -    'legacy-user-data': ('user-data', False), -    'user-data': ('cloud-init:user-data', False), -    'iptables_disable': ('iptables_disable', True), -    'motd_sys_info': ('motd_sys_info', True), -    'availability_zone': ('sdc:datacenter_name', True), -    'vendor-data': ('sdc:vendor-data', False), -    'operator-script': ('sdc:operator-script', False), -    'hostname': ('sdc:hostname', True), -    'dns_domain': ('sdc:dns_domain', True), +    "instance-id": ("sdc:uuid", True), +    "local-hostname": ("hostname", True), +    "public-keys": ("root_authorized_keys", True), +    "user-script": ("user-script", False), +    "legacy-user-data": ("user-data", False), +    "user-data": ("cloud-init:user-data", False), +    "iptables_disable": ("iptables_disable", True), +    "motd_sys_info": ("motd_sys_info", True), +    "availability_zone": ("sdc:datacenter_name", True), +    "vendor-data": ("sdc:vendor-data", False), +    "operator-script": ("sdc:operator-script", False), +    "hostname": ("sdc:hostname", True), +    "dns_domain": ("sdc:dns_domain", True),  }  SMARTOS_ATTRIB_JSON = {      # Cloud-init Key : (SmartOS Key known JSON) -    'network-data': 'sdc:nics', -    'dns_servers': 'sdc:resolvers', -    'routes': 'sdc:routes', +    "network-data": "sdc:nics", +    "dns_servers": "sdc:resolvers", +    "routes": "sdc:routes",  }  SMARTOS_ENV_LX_BRAND = "lx-brand"  SMARTOS_ENV_KVM = "kvm" -DS_NAME = 'SmartOS' -DS_CFG_PATH = ['datasource', DS_NAME] +DS_NAME = "SmartOS" +DS_CFG_PATH = ["datasource", DS_NAME]  NO_BASE64_DECODE = [ -    'iptables_disable', -    'motd_sys_info', -    'root_authorized_keys', -    'sdc:datacenter_name', -    'sdc:uuid' -    'user-data', -    'user-script', +    "iptables_disable", +    "motd_sys_info", +    "root_authorized_keys", +    "sdc:datacenter_name", +    "sdc:uuiduser-data", +    "user-script",  ] -METADATA_SOCKFILE = '/native/.zonecontrol/metadata.sock' -SERIAL_DEVICE = '/dev/ttyS1' +METADATA_SOCKFILE = "/native/.zonecontrol/metadata.sock" +SERIAL_DEVICE = "/dev/ttyS1"  SERIAL_TIMEOUT = 60  # BUILT-IN DATASOURCE CONFIGURATION @@ -98,24 +94,26 @@ SERIAL_TIMEOUT = 60  #    fs_setup: describes how to format the ephemeral drive  #  BUILTIN_DS_CONFIG = { -    'serial_device': SERIAL_DEVICE, -    'serial_timeout': SERIAL_TIMEOUT, -    'metadata_sockfile': METADATA_SOCKFILE, -    'no_base64_decode': NO_BASE64_DECODE, -    'base64_keys': [], -    'base64_all': False, -    'disk_aliases': {'ephemeral0': '/dev/vdb'}, +    "serial_device": SERIAL_DEVICE, +    "serial_timeout": SERIAL_TIMEOUT, +    "metadata_sockfile": METADATA_SOCKFILE, +    "no_base64_decode": NO_BASE64_DECODE, +    "base64_keys": [], +    "base64_all": False, +    "disk_aliases": {"ephemeral0": "/dev/vdb"},  }  BUILTIN_CLOUD_CONFIG = { -    'disk_setup': { -        'ephemeral0': {'table_type': 'mbr', -                       'layout': False, -                       'overwrite': False} +    "disk_setup": { +        "ephemeral0": { +            "table_type": "mbr", +            "layout": False, +            "overwrite": False, +        }      }, -    'fs_setup': [{'label': 'ephemeral0', -                  'filesystem': 'ext4', -                  'device': 'ephemeral0'}], +    "fs_setup": [ +        {"label": "ephemeral0", "filesystem": "ext4", "device": "ephemeral0"} +    ],  }  # builtin vendor-data is a boothook that writes a script into @@ -170,18 +168,23 @@ class DataSourceSmartOS(sources.DataSource):      smartos_type = sources.UNSET      md_client = sources.UNSET -    default_update_events = {EventScope.NETWORK: { -        EventType.BOOT_NEW_INSTANCE, -        EventType.BOOT, -        EventType.BOOT_LEGACY -    }} +    default_update_events = { +        EventScope.NETWORK: { +            EventType.BOOT_NEW_INSTANCE, +            EventType.BOOT, +            EventType.BOOT_LEGACY, +        } +    }      def __init__(self, sys_cfg, distro, paths):          sources.DataSource.__init__(self, sys_cfg, distro, paths) -        self.ds_cfg = util.mergemanydict([ -            self.ds_cfg, -            util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}), -            BUILTIN_DS_CONFIG]) +        self.ds_cfg = util.mergemanydict( +            [ +                self.ds_cfg, +                util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}), +                BUILTIN_DS_CONFIG, +            ] +        )          self.metadata = {}          self.network_data = None @@ -204,25 +207,28 @@ class DataSourceSmartOS(sources.DataSource):          if self.md_client == sources.UNSET:              self.md_client = jmc_client_factory(                  smartos_type=self.smartos_type, -                metadata_sockfile=self.ds_cfg['metadata_sockfile'], -                serial_device=self.ds_cfg['serial_device'], -                serial_timeout=self.ds_cfg['serial_timeout']) +                metadata_sockfile=self.ds_cfg["metadata_sockfile"], +                serial_device=self.ds_cfg["serial_device"], +                serial_timeout=self.ds_cfg["serial_timeout"], +            )      def _set_provisioned(self): -        '''Mark the instance provisioning state as successful. +        """Mark the instance provisioning state as successful.          When run in a zone, the host OS will look for /var/svc/provisioning          to be renamed as /var/svc/provision_success.   This should be done          after meta-data is successfully retrieved and from this point          the host considers the provision of the zone to be a success and          keeps the zone running. -        ''' +        """ -        LOG.debug('Instance provisioning state set as successful') -        svc_path = '/var/svc' -        if os.path.exists('/'.join([svc_path, 'provisioning'])): -            os.rename('/'.join([svc_path, 'provisioning']), -                      '/'.join([svc_path, 'provision_success'])) +        LOG.debug("Instance provisioning state set as successful") +        svc_path = "/var/svc" +        if os.path.exists("/".join([svc_path, "provisioning"])): +            os.rename( +                "/".join([svc_path, "provisioning"]), +                "/".join([svc_path, "provision_success"]), +            )      def _get_data(self):          self._init() @@ -235,8 +241,10 @@ class DataSourceSmartOS(sources.DataSource):              return False          if not self.md_client.exists(): -            LOG.debug("No metadata device '%r' found for SmartOS datasource", -                      self.md_client) +            LOG.debug( +                "No metadata device '%r' found for SmartOS datasource", +                self.md_client, +            )              return False          # Open once for many requests, rather than once for each request @@ -259,24 +267,33 @@ class DataSourceSmartOS(sources.DataSource):          # We write 'user-script' and 'operator-script' into the          # instance/data directory. The default vendor-data then handles          # executing them later. -        data_d = os.path.join(self.paths.get_cpath(), 'instances', -                              md['instance-id'], 'data') -        user_script = os.path.join(data_d, 'user-script') +        data_d = os.path.join( +            self.paths.get_cpath(), "instances", md["instance-id"], "data" +        ) +        user_script = os.path.join(data_d, "user-script")          u_script_l = "%s/user-script" % LEGACY_USER_D -        write_boot_content(md.get('user-script'), content_f=user_script, -                           link=u_script_l, shebang=True, mode=0o700) - -        operator_script = os.path.join(data_d, 'operator-script') -        write_boot_content(md.get('operator-script'), -                           content_f=operator_script, shebang=False, -                           mode=0o700) +        write_boot_content( +            md.get("user-script"), +            content_f=user_script, +            link=u_script_l, +            shebang=True, +            mode=0o700, +        ) + +        operator_script = os.path.join(data_d, "operator-script") +        write_boot_content( +            md.get("operator-script"), +            content_f=operator_script, +            shebang=False, +            mode=0o700, +        )          # @datadictionary:  This key has no defined format, but its value          # is written to the file /var/db/mdata-user-data on each boot prior          # to the phase that runs user-script. This file is not to be executed.          # This allows a configuration file of some kind to be injected into          # the machine to be consumed by the user-script when it runs. -        u_data = md.get('legacy-user-data') +        u_data = md.get("legacy-user-data")          u_data_f = "%s/mdata-user-data" % LEGACY_USER_D          write_boot_content(u_data, u_data_f) @@ -284,38 +301,39 @@ class DataSourceSmartOS(sources.DataSource):          # The hostname may or may not be qualified with the local domain name.          # This follows section 3.14 of RFC 2132. -        if not md['local-hostname']: -            if md['hostname']: -                md['local-hostname'] = md['hostname'] +        if not md["local-hostname"]: +            if md["hostname"]: +                md["local-hostname"] = md["hostname"]              else: -                md['local-hostname'] = md['instance-id'] +                md["local-hostname"] = md["instance-id"]          ud = None -        if md['user-data']: -            ud = md['user-data'] - -        if not md['vendor-data']: -            md['vendor-data'] = BUILTIN_VENDOR_DATA % { -                'user_script': user_script, -                'operator_script': operator_script, -                'per_boot_d': os.path.join(self.paths.get_cpath("scripts"), -                                           'per-boot'), +        if md["user-data"]: +            ud = md["user-data"] + +        if not md["vendor-data"]: +            md["vendor-data"] = BUILTIN_VENDOR_DATA % { +                "user_script": user_script, +                "operator_script": operator_script, +                "per_boot_d": os.path.join( +                    self.paths.get_cpath("scripts"), "per-boot" +                ),              }          self.metadata = util.mergemanydict([md, self.metadata])          self.userdata_raw = ud -        self.vendordata_raw = md['vendor-data'] -        self.network_data = md['network-data'] -        self.routes_data = md['routes'] +        self.vendordata_raw = md["vendor-data"] +        self.network_data = md["network-data"] +        self.routes_data = md["routes"]          self._set_provisioned()          return True      def _get_subplatform(self): -        return 'serial (%s)' % SERIAL_DEVICE +        return "serial (%s)" % SERIAL_DEVICE      def device_name_to_device(self, name): -        return self.ds_cfg['disk_aliases'].get(name) +        return self.ds_cfg["disk_aliases"].get(name)      def get_config_obj(self):          if self.smartos_type == SMARTOS_ENV_KVM: @@ -323,7 +341,7 @@ class DataSourceSmartOS(sources.DataSource):          return {}      def get_instance_id(self): -        return self.metadata['instance-id'] +        return self.metadata["instance-id"]      @property      def network_config(self): @@ -333,12 +351,12 @@ class DataSourceSmartOS(sources.DataSource):          if self._network_config is None:              if self.network_data is not None: -                self._network_config = ( -                    convert_smartos_network_data( -                        network_data=self.network_data, -                        dns_servers=self.metadata['dns_servers'], -                        dns_domain=self.metadata['dns_domain'], -                        routes=self.routes_data)) +                self._network_config = convert_smartos_network_data( +                    network_data=self.network_data, +                    dns_servers=self.metadata["dns_servers"], +                    dns_domain=self.metadata["dns_domain"], +                    routes=self.routes_data, +                )          return self._network_config @@ -357,10 +375,12 @@ class JoyentMetadataClient(object):      The full specification can be found at      http://eng.joyent.com/mdata/protocol.html      """ +      line_regex = re.compile( -        r'V2 (?P<length>\d+) (?P<checksum>[0-9a-f]+)' -        r' (?P<body>(?P<request_id>[0-9a-f]+) (?P<status>SUCCESS|NOTFOUND)' -        r'( (?P<payload>.+))?)') +        r"V2 (?P<length>\d+) (?P<checksum>[0-9a-f]+)" +        r" (?P<body>(?P<request_id>[0-9a-f]+) (?P<status>SUCCESS|NOTFOUND)" +        r"( (?P<payload>.+))?)" +    )      def __init__(self, smartos_type=None, fp=None):          if smartos_type is None: @@ -369,43 +389,50 @@ class JoyentMetadataClient(object):          self.fp = fp      def _checksum(self, body): -        return '{0:08x}'.format( -            binascii.crc32(body.encode('utf-8')) & 0xffffffff) +        return "{0:08x}".format( +            binascii.crc32(body.encode("utf-8")) & 0xFFFFFFFF +        )      def _get_value_from_frame(self, expected_request_id, frame):          frame_data = self.line_regex.match(frame).groupdict() -        if int(frame_data['length']) != len(frame_data['body']): +        if int(frame_data["length"]) != len(frame_data["body"]):              raise JoyentMetadataFetchException( -                'Incorrect frame length given ({0} != {1}).'.format( -                    frame_data['length'], len(frame_data['body']))) -        expected_checksum = self._checksum(frame_data['body']) -        if frame_data['checksum'] != expected_checksum: +                "Incorrect frame length given ({0} != {1}).".format( +                    frame_data["length"], len(frame_data["body"]) +                ) +            ) +        expected_checksum = self._checksum(frame_data["body"]) +        if frame_data["checksum"] != expected_checksum:              raise JoyentMetadataFetchException( -                'Invalid checksum (expected: {0}; got {1}).'.format( -                    expected_checksum, frame_data['checksum'])) -        if frame_data['request_id'] != expected_request_id: +                "Invalid checksum (expected: {0}; got {1}).".format( +                    expected_checksum, frame_data["checksum"] +                ) +            ) +        if frame_data["request_id"] != expected_request_id:              raise JoyentMetadataFetchException( -                'Request ID mismatch (expected: {0}; got {1}).'.format( -                    expected_request_id, frame_data['request_id'])) -        if not frame_data.get('payload', None): -            LOG.debug('No value found.') +                "Request ID mismatch (expected: {0}; got {1}).".format( +                    expected_request_id, frame_data["request_id"] +                ) +            ) +        if not frame_data.get("payload", None): +            LOG.debug("No value found.")              return None -        value = util.b64d(frame_data['payload']) +        value = util.b64d(frame_data["payload"])          LOG.debug('Value "%s" found.', value)          return value      def _readline(self):          """ -           Reads a line a byte at a time until \n is encountered.  Returns an -           ascii string with the trailing newline removed. +        Reads a line a byte at a time until \n is encountered.  Returns an +        ascii string with the trailing newline removed. -           If a timeout (per-byte) is set and it expires, a -           JoyentMetadataFetchException will be thrown. +        If a timeout (per-byte) is set and it expires, a +        JoyentMetadataFetchException will be thrown.          """          response = []          def as_ascii(): -            return b''.join(response).decode('ascii') +            return b"".join(response).decode("ascii")          msg = "Partial response: '%s'"          while True: @@ -413,7 +440,7 @@ class JoyentMetadataClient(object):                  byte = self.fp.read(1)                  if len(byte) == 0:                      raise JoyentMetadataTimeoutException(msg % as_ascii()) -                if byte == b'\n': +                if byte == b"\n":                      return as_ascii()                  response.append(byte)              except OSError as exc: @@ -424,26 +451,33 @@ class JoyentMetadataClient(object):                  raise      def _write(self, msg): -        self.fp.write(msg.encode('ascii')) +        self.fp.write(msg.encode("ascii"))          self.fp.flush()      def _negotiate(self): -        LOG.debug('Negotiating protocol V2') -        self._write('NEGOTIATE V2\n') +        LOG.debug("Negotiating protocol V2") +        self._write("NEGOTIATE V2\n")          response = self._readline()          LOG.debug('read "%s"', response) -        if response != 'V2_OK': +        if response != "V2_OK":              raise JoyentMetadataFetchException( -                'Invalid response "%s" to "NEGOTIATE V2"' % response) -        LOG.debug('Negotiation complete') +                'Invalid response "%s" to "NEGOTIATE V2"' % response +            ) +        LOG.debug("Negotiation complete")      def request(self, rtype, param=None): -        request_id = '{0:08x}'.format(random.randint(0, 0xffffffff)) -        message_body = ' '.join((request_id, rtype,)) +        request_id = "{0:08x}".format(random.randint(0, 0xFFFFFFFF)) +        message_body = " ".join( +            ( +                request_id, +                rtype, +            ) +        )          if param: -            message_body += ' ' + base64.b64encode(param.encode()).decode() -        msg = 'V2 {0} {1} {2}\n'.format( -            len(message_body), self._checksum(message_body), message_body) +            message_body += " " + base64.b64encode(param.encode()).decode() +        msg = "V2 {0} {1} {2}\n".format( +            len(message_body), self._checksum(message_body), message_body +        )          LOG.debug('Writing "%s" to metadata transport.', msg)          need_close = False @@ -458,14 +492,14 @@ class JoyentMetadataClient(object):          LOG.debug('Read "%s" from metadata transport.', response) -        if 'SUCCESS' not in response: +        if "SUCCESS" not in response:              return None          value = self._get_value_from_frame(request_id, response)          return value      def get(self, key, default=None, strip=False): -        result = self.request(rtype='GET', param=key) +        result = self.request(rtype="GET", param=key)          if result is None:              return default          if result and strip: @@ -479,18 +513,19 @@ class JoyentMetadataClient(object):          return json.loads(result)      def list(self): -        result = self.request(rtype='KEYS') +        result = self.request(rtype="KEYS")          if not result:              return [] -        return result.split('\n') +        return result.split("\n")      def put(self, key, val): -        param = b' '.join([base64.b64encode(i.encode()) -                           for i in (key, val)]).decode() -        return self.request(rtype='PUT', param=param) +        param = b" ".join( +            [base64.b64encode(i.encode()) for i in (key, val)] +        ).decode() +        return self.request(rtype="PUT", param=param)      def delete(self, key): -        return self.request(rtype='DELETE', param=key) +        return self.request(rtype="DELETE", param=key)      def close_transport(self):          if self.fp: @@ -519,7 +554,7 @@ class JoyentMetadataSocketClient(JoyentMetadataClient):      def open_transport(self):          sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)          sock.connect(self.socketpath) -        self.fp = sock.makefile('rwb') +        self.fp = sock.makefile("rwb")          self._negotiate()      def exists(self): @@ -530,8 +565,9 @@ class JoyentMetadataSocketClient(JoyentMetadataClient):  class JoyentMetadataSerialClient(JoyentMetadataClient): -    def __init__(self, device, timeout=10, smartos_type=SMARTOS_ENV_KVM, -                 fp=None): +    def __init__( +        self, device, timeout=10, smartos_type=SMARTOS_ENV_KVM, fp=None +    ):          super(JoyentMetadataSerialClient, self).__init__(smartos_type, fp)          self.device = device          self.timeout = timeout @@ -550,7 +586,7 @@ class JoyentMetadataSerialClient(JoyentMetadataClient):          self._negotiate()      def _flush(self): -        LOG.debug('Flushing input') +        LOG.debug("Flushing input")          # Read any pending data          timeout = self.fp.timeout          self.fp.timeout = 0.1 @@ -559,7 +595,7 @@ class JoyentMetadataSerialClient(JoyentMetadataClient):                  self._readline()              except JoyentMetadataTimeoutException:                  break -        LOG.debug('Input empty') +        LOG.debug("Input empty")          # Send a newline and expect "invalid command".  Keep trying until          # successful.  Retry rather frequently so that the "Is the host @@ -571,24 +607,29 @@ class JoyentMetadataSerialClient(JoyentMetadataClient):              self.fp.timeout = timeout          while True:              LOG.debug('Writing newline, expecting "invalid command"') -            self._write('\n') +            self._write("\n")              try:                  response = self._readline() -                if response == 'invalid command': +                if response == "invalid command":                      break -                if response == 'FAILURE': +                if response == "FAILURE":                      LOG.debug('Got "FAILURE".  Retrying.')                      continue                  LOG.warning('Unexpected response "%s" during flush', response)              except JoyentMetadataTimeoutException: -                LOG.warning('Timeout while initializing metadata client. ' -                            'Is the host metadata service running?') +                LOG.warning( +                    "Timeout while initializing metadata client. " +                    "Is the host metadata service running?" +                )          LOG.debug('Got "invalid command".  Flush complete.')          self.fp.timeout = timeout      def __repr__(self):          return "%s(device=%s, timeout=%s)" % ( -            self.__class__.__name__, self.device, self.timeout) +            self.__class__.__name__, +            self.device, +            self.timeout, +        )  class JoyentMetadataLegacySerialClient(JoyentMetadataSerialClient): @@ -620,7 +661,7 @@ class JoyentMetadataLegacySerialClient(JoyentMetadataSerialClient):          keys = None          if self.base64_all is None:              keys = self.list() -            if 'base64_all' in keys: +            if "base64_all" in keys:                  self.base64_all = util.is_true(self._get("base64_all"))              else:                  self.base64_all = False @@ -633,7 +674,7 @@ class JoyentMetadataLegacySerialClient(JoyentMetadataSerialClient):              if keys is None:                  keys = self.list()              b64_keys = set() -            if 'base64_keys' in keys: +            if "base64_keys" in keys:                  b64_keys = set(self._get("base64_keys").split(","))              # now add any b64-<keyname> that has a true value @@ -647,8 +688,9 @@ class JoyentMetadataLegacySerialClient(JoyentMetadataSerialClient):              self.base64_keys = b64_keys      def _get(self, key, default=None, strip=False): -        return (super(JoyentMetadataLegacySerialClient, self). -                get(key, default=default, strip=strip)) +        return super(JoyentMetadataLegacySerialClient, self).get( +            key, default=default, strip=strip +        )      def is_b64_encoded(self, key, reset=False):          if key in NO_BASE64_DECODE: @@ -680,9 +722,12 @@ class JoyentMetadataLegacySerialClient(JoyentMetadataSerialClient):  def jmc_client_factory( -        smartos_type=None, metadata_sockfile=METADATA_SOCKFILE, -        serial_device=SERIAL_DEVICE, serial_timeout=SERIAL_TIMEOUT, -        uname_version=None): +    smartos_type=None, +    metadata_sockfile=METADATA_SOCKFILE, +    serial_device=SERIAL_DEVICE, +    serial_timeout=SERIAL_TIMEOUT, +    uname_version=None, +):      if smartos_type is None:          smartos_type = get_smartos_environ(uname_version) @@ -691,11 +736,14 @@ def jmc_client_factory(          return None      elif smartos_type == SMARTOS_ENV_KVM:          return JoyentMetadataLegacySerialClient( -            device=serial_device, timeout=serial_timeout, -            smartos_type=smartos_type) +            device=serial_device, +            timeout=serial_timeout, +            smartos_type=smartos_type, +        )      elif smartos_type == SMARTOS_ENV_LX_BRAND: -        return JoyentMetadataSocketClient(socketpath=metadata_sockfile, -                                          smartos_type=smartos_type) +        return JoyentMetadataSocketClient( +            socketpath=metadata_sockfile, smartos_type=smartos_type +        )      raise ValueError("Unknown value for smartos_type: %s" % smartos_type) @@ -708,12 +756,14 @@ def identify_file(content_f):          LOG.debug("script %s mime type is %s", content_f, f_type)      except subp.ProcessExecutionError as e:          util.logexc( -            LOG, ("Failed to identify script type for %s" % content_f, e)) +            LOG, ("Failed to identify script type for %s" % content_f, e) +        )      return None if f_type is None else f_type.strip() -def write_boot_content(content, content_f, link=None, shebang=False, -                       mode=0o400): +def write_boot_content( +    content, content_f, link=None, shebang=False, mode=0o400 +):      """      Write the content to content_f. Under the following rules:          1. If no content, remove the file @@ -747,7 +797,8 @@ def write_boot_content(content, content_f, link=None, shebang=False,          f_type = identify_file(content_f)          if f_type == "text/plain":              util.write_file( -                content_f, "\n".join(["#!/bin/bash", content]), mode=mode) +                content_f, "\n".join(["#!/bin/bash", content]), mode=mode +            )              LOG.debug("added shebang to file %s", content_f)      if link: @@ -768,7 +819,7 @@ def get_smartos_environ(uname_version=None, product_name=None):      # report 'BrandZ virtual linux' as the kernel version      if uname_version is None:          uname_version = uname[3] -    if uname_version == 'BrandZ virtual linux': +    if uname_version == "BrandZ virtual linux":          return SMARTOS_ENV_LX_BRAND      if product_name is None: @@ -776,16 +827,16 @@ def get_smartos_environ(uname_version=None, product_name=None):      else:          system_type = product_name -    if system_type and system_type.startswith('SmartDC'): +    if system_type and system_type.startswith("SmartDC"):          return SMARTOS_ENV_KVM      return None  # Convert SMARTOS 'sdc:nics' data to network_config yaml -def convert_smartos_network_data(network_data=None, -                                 dns_servers=None, dns_domain=None, -                                 routes=None): +def convert_smartos_network_data( +    network_data=None, dns_servers=None, dns_domain=None, routes=None +):      """Return a dictionary of network_config by parsing provided         SMARTOS sdc:nics configuration data @@ -810,28 +861,28 @@ def convert_smartos_network_data(network_data=None,      """      valid_keys = { -        'physical': [ -            'mac_address', -            'mtu', -            'name', -            'params', -            'subnets', -            'type', +        "physical": [ +            "mac_address", +            "mtu", +            "name", +            "params", +            "subnets", +            "type",          ], -        'subnet': [ -            'address', -            'broadcast', -            'dns_nameservers', -            'dns_search', -            'metric', -            'pointopoint', -            'routes', -            'scope', -            'type', +        "subnet": [ +            "address", +            "broadcast", +            "dns_nameservers", +            "dns_search", +            "metric", +            "pointopoint", +            "routes", +            "scope", +            "type",          ], -        'route': [ -            'network', -            'gateway', +        "route": [ +            "network", +            "gateway",          ],      } @@ -851,56 +902,64 @@ def convert_smartos_network_data(network_data=None,          routes = []      def is_valid_ipv4(addr): -        return '.' in addr +        return "." in addr      def is_valid_ipv6(addr): -        return ':' in addr +        return ":" in addr      pgws = { -        'ipv4': {'match': is_valid_ipv4, 'gw': None}, -        'ipv6': {'match': is_valid_ipv6, 'gw': None}, +        "ipv4": {"match": is_valid_ipv4, "gw": None}, +        "ipv6": {"match": is_valid_ipv6, "gw": None},      }      config = []      for nic in network_data: -        cfg = dict((k, v) for k, v in nic.items() -                   if k in valid_keys['physical']) -        cfg.update({ -            'type': 'physical', -            'name': nic['interface']}) -        if 'mac' in nic: -            cfg.update({'mac_address': nic['mac']}) +        cfg = dict( +            (k, v) for k, v in nic.items() if k in valid_keys["physical"] +        ) +        cfg.update({"type": "physical", "name": nic["interface"]}) +        if "mac" in nic: +            cfg.update({"mac_address": nic["mac"]})          subnets = [] -        for ip in nic.get('ips', []): +        for ip in nic.get("ips", []):              if ip == "dhcp": -                subnet = {'type': 'dhcp4'} +                subnet = {"type": "dhcp4"}              else:                  routeents = [] -                subnet = dict((k, v) for k, v in nic.items() -                              if k in valid_keys['subnet']) -                subnet.update({ -                    'type': 'static', -                    'address': ip, -                }) - -                proto = 'ipv4' if is_valid_ipv4(ip) else 'ipv6' +                subnet = dict( +                    (k, v) for k, v in nic.items() if k in valid_keys["subnet"] +                ) +                subnet.update( +                    { +                        "type": "static", +                        "address": ip, +                    } +                ) + +                proto = "ipv4" if is_valid_ipv4(ip) else "ipv6"                  # Only use gateways for 'primary' nics -                if 'primary' in nic and nic.get('primary', False): +                if "primary" in nic and nic.get("primary", False):                      # the ips and gateways list may be N to M, here                      # we map the ip index into the gateways list,                      # and handle the case that we could have more ips                      # than gateways.  we only consume the first gateway -                    if not pgws[proto]['gw']: -                        gateways = [gw for gw in nic.get('gateways', []) -                                    if pgws[proto]['match'](gw)] +                    if not pgws[proto]["gw"]: +                        gateways = [ +                            gw +                            for gw in nic.get("gateways", []) +                            if pgws[proto]["match"](gw) +                        ]                          if len(gateways): -                            pgws[proto]['gw'] = gateways[0] -                            subnet.update({'gateway': pgws[proto]['gw']}) +                            pgws[proto]["gw"] = gateways[0] +                            subnet.update({"gateway": pgws[proto]["gw"]})                  for route in routes: -                    rcfg = dict((k, v) for k, v in route.items() -                                if k in valid_keys['route']) +                    rcfg = dict( +                        (k, v) +                        for k, v in route.items() +                        if k in valid_keys["route"] +                    )                      # Linux uses the value of 'gateway' to determine                      # automatically if the route is a forward/next-hop                      # (non-local IP for gateway) or an interface/resolver @@ -913,25 +972,29 @@ def convert_smartos_network_data(network_data=None,                      # to see if it's in the prefix.  We can then smartly                      # add or not-add this route.  But for now,                      # when in doubt, use brute force! Routes for everyone! -                    rcfg.update({'network': route['dst']}) +                    rcfg.update({"network": route["dst"]})                      routeents.append(rcfg) -                    subnet.update({'routes': routeents}) +                    subnet.update({"routes": routeents})              subnets.append(subnet) -        cfg.update({'subnets': subnets}) +        cfg.update({"subnets": subnets})          config.append(cfg)      if dns_servers:          config.append( -            {'type': 'nameserver', 'address': dns_servers, -             'search': dns_domain}) +            { +                "type": "nameserver", +                "address": dns_servers, +                "search": dns_domain, +            } +        ) -    return {'version': 1, 'config': config} +    return {"version": 1, "config": config}  # Used to match classes to dependencies  datasources = [ -    (DataSourceSmartOS, (sources.DEP_FILESYSTEM, )), +    (DataSourceSmartOS, (sources.DEP_FILESYSTEM,)),  ] @@ -942,13 +1005,17 @@ def get_datasource_list(depends):  if __name__ == "__main__":      import sys +      jmc = jmc_client_factory()      if jmc is None:          print("Do not appear to be on smartos.")          sys.exit(1)      if len(sys.argv) == 1: -        keys = (list(SMARTOS_ATTRIB_JSON.keys()) + -                list(SMARTOS_ATTRIB_MAP.keys()) + ['network_config']) +        keys = ( +            list(SMARTOS_ATTRIB_JSON.keys()) +            + list(SMARTOS_ATTRIB_MAP.keys()) +            + ["network_config"] +        )      else:          keys = sys.argv[1:] @@ -960,14 +1027,19 @@ if __name__ == "__main__":              keyname = SMARTOS_ATTRIB_JSON[key]              data[key] = client.get_json(keyname)          elif key == "network_config": -            for depkey in ('network-data', 'dns_servers', 'dns_domain', -                           'routes'): +            for depkey in ( +                "network-data", +                "dns_servers", +                "dns_domain", +                "routes", +            ):                  load_key(client, depkey, data)              data[key] = convert_smartos_network_data( -                network_data=data['network-data'], -                dns_servers=data['dns_servers'], -                dns_domain=data['dns_domain'], -                routes=data['routes']) +                network_data=data["network-data"], +                dns_servers=data["dns_servers"], +                dns_domain=data["dns_domain"], +                routes=data["routes"], +            )          else:              if key in SMARTOS_ATTRIB_MAP:                  keyname, strip = SMARTOS_ATTRIB_MAP[key] @@ -981,7 +1053,6 @@ if __name__ == "__main__":      for key in keys:          load_key(client=jmc, key=key, data=data) -    print(json.dumps(data, indent=1, sort_keys=True, -                     separators=(',', ': '))) +    print(json.dumps(data, indent=1, sort_keys=True, separators=(",", ": ")))  # vi: ts=4 expandtab | 
