~harlowja/cloud-init/cloud-init-tag-distros

« back to all changes in this revision

Viewing changes to cloudinit/sources/DataSourceSmartOS.py

improve network configuration

This branch accomplishes several things:
 - centrally handle 'dsmode' to be 'local' or 'net.
   This allows local data sources to run before networking
   but still have user-data read by default when networking is available.

 - support networking information being read on dreamcompute
   dreamcompute's openstack declares networking via the
   /etc/network/interfaces style 'network_config' format.
 
 - support reading and applying networking information on SmartOS

 - improve reading networking from openstack network_data.json (LP: #1577982)
   add support for mtu and routes and many miscellaneous fixes.

 - support for renaming devices in a container (LP: #1579130).
   Also rename network devices as instructed by the host on
   every boot where cloud-init networking is enabled.  This is required
   because a.) containers do not get systemd.link files applied
   as they do not have udev. b.) if the initramfs is out of date
   then we need to apply them.

 - remove blocking of udev rules (LP: #1577844, LP: #1571761)

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
#       http://us-east.manta.joyent.com/jmc/public/mdata/datadict.html
33
33
#       Comments with "@datadictionary" are snippets of the definition
34
34
 
 
35
import base64
35
36
import binascii
36
 
import contextlib
 
37
import json
37
38
import os
38
39
import random
39
40
import re
40
41
import socket
41
 
import stat
42
42
 
43
43
import serial
44
44
 
64
64
    'operator-script': ('sdc:operator-script', False),
65
65
}
66
66
 
 
67
SMARTOS_ATTRIB_JSON = {
 
68
    # Cloud-init Key : (SmartOS Key known JSON)
 
69
    'network-data': 'sdc:nics',
 
70
}
 
71
 
 
72
SMARTOS_ENV_LX_BRAND = "lx-brand"
 
73
SMARTOS_ENV_KVM = "kvm"
 
74
 
67
75
DS_NAME = 'SmartOS'
68
76
DS_CFG_PATH = ['datasource', DS_NAME]
 
77
NO_BASE64_DECODE = [
 
78
    'iptables_disable',
 
79
    'motd_sys_info',
 
80
    'root_authorized_keys',
 
81
    'sdc:datacenter_name',
 
82
    'sdc:uuid'
 
83
    'user-data',
 
84
    'user-script',
 
85
]
 
86
 
 
87
METADATA_SOCKFILE = '/native/.zonecontrol/metadata.sock'
 
88
SERIAL_DEVICE = '/dev/ttyS1'
 
89
SERIAL_TIMEOUT = 60
 
90
 
69
91
# BUILT-IN DATASOURCE CONFIGURATION
70
92
#  The following is the built-in configuration. If the values
71
93
#  are not set via the system configuration, then these default
72
94
#  will be used:
73
95
#    serial_device: which serial device to use for the meta-data
74
 
#    seed_timeout: how long to wait on the device
 
96
#    serial_timeout: how long to wait on the device
75
97
#    no_base64_decode: values which are not base64 encoded and
76
98
#            are fetched directly from SmartOS, not meta-data values
77
99
#    base64_keys: meta-data keys that are delivered in base64
81
103
#    fs_setup: describes how to format the ephemeral drive
82
104
#
83
105
BUILTIN_DS_CONFIG = {
84
 
    'serial_device': '/dev/ttyS1',
85
 
    'metadata_sockfile': '/native/.zonecontrol/metadata.sock',
86
 
    'seed_timeout': 60,
87
 
    'no_base64_decode': ['root_authorized_keys',
88
 
                         'motd_sys_info',
89
 
                         'iptables_disable',
90
 
                         'user-data',
91
 
                         'user-script',
92
 
                         'sdc:datacenter_name',
93
 
                         'sdc:uuid'],
 
106
    'serial_device': SERIAL_DEVICE,
 
107
    'serial_timeout': SERIAL_TIMEOUT,
 
108
    'metadata_sockfile': METADATA_SOCKFILE,
 
109
    'no_base64_decode': NO_BASE64_DECODE,
94
110
    'base64_keys': [],
95
111
    'base64_all': False,
96
112
    'disk_aliases': {'ephemeral0': '/dev/vdb'},
154
170
 
155
171
 
156
172
class DataSourceSmartOS(sources.DataSource):
 
173
    _unset = "_unset"
 
174
    smartos_type = _unset
 
175
    md_client = _unset
 
176
 
157
177
    def __init__(self, sys_cfg, distro, paths):
158
178
        sources.DataSource.__init__(self, sys_cfg, distro, paths)
159
 
        self.is_smartdc = None
160
179
        self.ds_cfg = util.mergemanydict([
161
180
            self.ds_cfg,
162
181
            util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}),
163
182
            BUILTIN_DS_CONFIG])
164
183
 
165
184
        self.metadata = {}
 
185
        self.network_data = None
 
186
        self._network_config = None
166
187
 
167
 
        # SDC LX-Brand Zones lack dmidecode (no /dev/mem) but
168
 
        # report 'BrandZ virtual linux' as the kernel version
169
 
        if os.uname()[3].lower() == 'brandz virtual linux':
170
 
            LOG.debug("Host is SmartOS, guest in Zone")
171
 
            self.is_smartdc = True
172
 
            self.smartos_type = 'lx-brand'
173
 
            self.cfg = {}
174
 
            self.seed = self.ds_cfg.get("metadata_sockfile")
175
 
        else:
176
 
            self.is_smartdc = True
177
 
            self.smartos_type = 'kvm'
178
 
            self.seed = self.ds_cfg.get("serial_device")
179
 
            self.cfg = BUILTIN_CLOUD_CONFIG
180
 
            self.seed_timeout = self.ds_cfg.get("serial_timeout")
181
 
        self.smartos_no_base64 = self.ds_cfg.get('no_base64_decode')
182
 
        self.b64_keys = self.ds_cfg.get('base64_keys')
183
 
        self.b64_all = self.ds_cfg.get('base64_all')
184
188
        self.script_base_d = os.path.join(self.paths.get_cpath("scripts"))
185
189
 
 
190
        self._init()
 
191
 
186
192
    def __str__(self):
187
193
        root = sources.DataSource.__str__(self)
188
 
        return "%s [seed=%s]" % (root, self.seed)
189
 
 
190
 
    def _get_seed_file_object(self):
191
 
        if not self.seed:
192
 
            raise AttributeError("seed device is not set")
193
 
 
194
 
        if self.smartos_type == 'lx-brand':
195
 
            if not stat.S_ISSOCK(os.stat(self.seed).st_mode):
196
 
                LOG.debug("Seed %s is not a socket", self.seed)
197
 
                return None
198
 
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
199
 
            sock.connect(self.seed)
200
 
            return sock.makefile('rwb')
201
 
        else:
202
 
            if not stat.S_ISCHR(os.stat(self.seed).st_mode):
203
 
                LOG.debug("Seed %s is not a character device")
204
 
                return None
205
 
            ser = serial.Serial(self.seed, timeout=self.seed_timeout)
206
 
            if not ser.isOpen():
207
 
                raise SystemError("Unable to open %s" % self.seed)
208
 
            return ser
209
 
        return None
 
194
        return "%s [client=%s]" % (root, self.md_client)
 
195
 
 
196
    def _init(self):
 
197
        if self.smartos_type == self._unset:
 
198
            self.smartos_type = get_smartos_environ()
 
199
            if self.smartos_type is None:
 
200
                self.md_client = None
 
201
 
 
202
        if self.md_client == self._unset:
 
203
            self.md_client = jmc_client_factory(
 
204
                smartos_type=self.smartos_type,
 
205
                metadata_sockfile=self.ds_cfg['metadata_sockfile'],
 
206
                serial_device=self.ds_cfg['serial_device'],
 
207
                serial_timeout=self.ds_cfg['serial_timeout'])
210
208
 
211
209
    def _set_provisioned(self):
212
210
        '''Mark the instance provisioning state as successful.
225
223
                      '/'.join([svc_path, 'provision_success']))
226
224
 
227
225
    def get_data(self):
 
226
        self._init()
 
227
 
228
228
        md = {}
229
229
        ud = ""
230
230
 
231
 
        if not device_exists(self.seed):
232
 
            LOG.debug("No metadata device '%s' found for SmartOS datasource",
233
 
                      self.seed)
234
 
            return False
235
 
 
236
 
        uname_arch = os.uname()[4]
237
 
        if uname_arch.startswith("arm") or uname_arch == "aarch64":
238
 
            # Disabling because dmidcode in dmi_data() crashes kvm process
239
 
            LOG.debug("Disabling SmartOS datasource on arm (LP: #1243287)")
240
 
            return False
241
 
 
242
 
        # SDC KVM instances will provide dmi data, LX-brand does not
243
 
        if self.smartos_type == 'kvm':
244
 
            dmi_info = dmi_data()
245
 
            if dmi_info is None:
246
 
                LOG.debug("No dmidata utility found")
247
 
                return False
248
 
 
249
 
            system_type = dmi_info
250
 
            if 'smartdc' not in system_type.lower():
251
 
                LOG.debug("Host is not on SmartOS. system_type=%s",
252
 
                          system_type)
253
 
                return False
254
 
            LOG.debug("Host is SmartOS, guest in KVM")
255
 
 
256
 
        seed_obj = self._get_seed_file_object()
257
 
        if seed_obj is None:
258
 
            LOG.debug('Seed file object not found.')
259
 
            return False
260
 
        with contextlib.closing(seed_obj) as seed:
261
 
            b64_keys = self.query('base64_keys', seed, strip=True, b64=False)
262
 
            if b64_keys is not None:
263
 
                self.b64_keys = [k.strip() for k in str(b64_keys).split(',')]
264
 
 
265
 
            b64_all = self.query('base64_all', seed, strip=True, b64=False)
266
 
            if b64_all is not None:
267
 
                self.b64_all = util.is_true(b64_all)
268
 
 
269
 
            for ci_noun, attribute in SMARTOS_ATTRIB_MAP.items():
270
 
                smartos_noun, strip = attribute
271
 
                md[ci_noun] = self.query(smartos_noun, seed, strip=strip)
 
231
        if not self.smartos_type:
 
232
            LOG.debug("Not running on smartos")
 
233
            return False
 
234
 
 
235
        if not self.md_client.exists():
 
236
            LOG.debug("No metadata device '%r' found for SmartOS datasource",
 
237
                      self.md_client)
 
238
            return False
 
239
 
 
240
        for ci_noun, attribute in SMARTOS_ATTRIB_MAP.items():
 
241
            smartos_noun, strip = attribute
 
242
            md[ci_noun] = self.md_client.get(smartos_noun, strip=strip)
 
243
 
 
244
        for ci_noun, smartos_noun in SMARTOS_ATTRIB_JSON.items():
 
245
            md[ci_noun] = self.md_client.get_json(smartos_noun)
272
246
 
273
247
        # @datadictionary: This key may contain a program that is written
274
248
        # to a file in the filesystem of the guest on each boot and then
318
292
        self.metadata = util.mergemanydict([md, self.metadata])
319
293
        self.userdata_raw = ud
320
294
        self.vendordata_raw = md['vendor-data']
 
295
        self.network_data = md['network-data']
321
296
 
322
297
        self._set_provisioned()
323
298
        return True
326
301
        return self.ds_cfg['disk_aliases'].get(name)
327
302
 
328
303
    def get_config_obj(self):
329
 
        return self.cfg
 
304
        if self.smartos_type == SMARTOS_ENV_KVM:
 
305
            return BUILTIN_CLOUD_CONFIG
 
306
        return {}
330
307
 
331
308
    def get_instance_id(self):
332
309
        return self.metadata['instance-id']
333
310
 
334
 
    def query(self, noun, seed_file, strip=False, default=None, b64=None):
335
 
        if b64 is None:
336
 
            if noun in self.smartos_no_base64:
337
 
                b64 = False
338
 
            elif self.b64_all or noun in self.b64_keys:
339
 
                b64 = True
340
 
 
341
 
        return self._query_data(noun, seed_file, strip=strip,
342
 
                                default=default, b64=b64)
343
 
 
344
 
    def _query_data(self, noun, seed_file, strip=False,
345
 
                    default=None, b64=None):
346
 
        """Makes a request via "GET <NOUN>"
347
 
 
348
 
           In the response, the first line is the status, while subsequent
349
 
           lines are is the value. A blank line with a "." is used to
350
 
           indicate end of response.
351
 
 
352
 
           If the response is expected to be base64 encoded, then set
353
 
           b64encoded to true. Unfortantely, there is no way to know if
354
 
           something is 100% encoded, so this method relies on being told
355
 
           if the data is base64 or not.
356
 
        """
357
 
 
358
 
        if not noun:
359
 
            return False
360
 
 
361
 
        response = JoyentMetadataClient(seed_file).get_metadata(noun)
362
 
 
363
 
        if response is None:
364
 
            return default
365
 
 
366
 
        if b64 is None:
367
 
            b64 = self._query_data('b64-%s' % noun, seed_file, b64=False,
368
 
                                   default=False, strip=True)
369
 
            b64 = util.is_true(b64)
370
 
 
371
 
        resp = None
372
 
        if b64 or strip:
373
 
            resp = "".join(response).rstrip()
374
 
        else:
375
 
            resp = "".join(response)
376
 
 
377
 
        if b64:
378
 
            try:
379
 
                return util.b64d(resp)
380
 
            # Bogus input produces different errors in Python 2 and 3;
381
 
            # catch both.
382
 
            except (TypeError, binascii.Error):
383
 
                LOG.warn("Failed base64 decoding key '%s'", noun)
384
 
                return resp
385
 
 
386
 
        return resp
387
 
 
388
 
 
389
 
def device_exists(device):
390
 
    """Symplistic method to determine if the device exists or not"""
391
 
    return os.path.exists(device)
 
311
    @property
 
312
    def network_config(self):
 
313
        if self._network_config is None:
 
314
            if self.network_data is not None:
 
315
                self._network_config = (
 
316
                    convert_smartos_network_data(self.network_data))
 
317
        return self._network_config
392
318
 
393
319
 
394
320
class JoyentMetadataFetchException(Exception):
407
333
        r' (?P<body>(?P<request_id>[0-9a-f]+) (?P<status>SUCCESS|NOTFOUND)'
408
334
        r'( (?P<payload>.+))?)')
409
335
 
410
 
    def __init__(self, metasource):
411
 
        self.metasource = metasource
 
336
    def __init__(self, smartos_type=None, fp=None):
 
337
        if smartos_type is None:
 
338
            smartos_type = get_smartos_environ()
 
339
        self.smartos_type = smartos_type
 
340
        self.fp = fp
412
341
 
413
342
    def _checksum(self, body):
414
343
        return '{0:08x}'.format(
436
365
        LOG.debug('Value "%s" found.', value)
437
366
        return value
438
367
 
439
 
    def get_metadata(self, metadata_key):
440
 
        LOG.debug('Fetching metadata key "%s"...', metadata_key)
 
368
    def request(self, rtype, param=None):
441
369
        request_id = '{0:08x}'.format(random.randint(0, 0xffffffff))
442
 
        message_body = '{0} GET {1}'.format(request_id,
443
 
                                            util.b64e(metadata_key))
 
370
        message_body = ' '.join((request_id, rtype,))
 
371
        if param:
 
372
            message_body += ' ' + base64.b64encode(param.encode()).decode()
444
373
        msg = 'V2 {0} {1} {2}\n'.format(
445
374
            len(message_body), self._checksum(message_body), message_body)
446
375
        LOG.debug('Writing "%s" to metadata transport.', msg)
447
 
        self.metasource.write(msg.encode('ascii'))
448
 
        self.metasource.flush()
 
376
 
 
377
        need_close = False
 
378
        if not self.fp:
 
379
            self.open_transport()
 
380
            need_close = True
 
381
 
 
382
        self.fp.write(msg.encode('ascii'))
 
383
        self.fp.flush()
449
384
 
450
385
        response = bytearray()
451
 
        response.extend(self.metasource.read(1))
 
386
        response.extend(self.fp.read(1))
452
387
        while response[-1:] != b'\n':
453
 
            response.extend(self.metasource.read(1))
 
388
            response.extend(self.fp.read(1))
 
389
 
 
390
        if need_close:
 
391
            self.close_transport()
 
392
 
454
393
        response = response.rstrip().decode('ascii')
455
394
        LOG.debug('Read "%s" from metadata transport.', response)
456
395
 
457
396
        if 'SUCCESS' not in response:
458
397
            return None
459
398
 
460
 
        return self._get_value_from_frame(request_id, response)
461
 
 
462
 
 
463
 
def dmi_data():
464
 
    sys_type = util.read_dmi_data("system-product-name")
465
 
 
466
 
    if not sys_type:
 
399
        value = self._get_value_from_frame(request_id, response)
 
400
        return value
 
401
 
 
402
    def get(self, key, default=None, strip=False):
 
403
        result = self.request(rtype='GET', param=key)
 
404
        if result is None:
 
405
            return default
 
406
        if result and strip:
 
407
            result = result.strip()
 
408
        return result
 
409
 
 
410
    def get_json(self, key, default=None):
 
411
        result = self.get(key, default=default)
 
412
        if result is None:
 
413
            return default
 
414
        return json.loads(result)
 
415
 
 
416
    def list(self):
 
417
        result = self.request(rtype='KEYS')
 
418
        if result:
 
419
            result = result.split('\n')
 
420
        return result
 
421
 
 
422
    def put(self, key, val):
 
423
        param = b' '.join([base64.b64encode(i.encode())
 
424
                           for i in (key, val)]).decode()
 
425
        return self.request(rtype='PUT', param=param)
 
426
 
 
427
    def delete(self, key):
 
428
        return self.request(rtype='DELETE', param=key)
 
429
 
 
430
    def close_transport(self):
 
431
        if self.fp:
 
432
            self.fp.close()
 
433
            self.fp = None
 
434
 
 
435
    def __enter__(self):
 
436
        if self.fp:
 
437
            return self
 
438
        self.open_transport()
 
439
        return self
 
440
 
 
441
    def __exit__(self, exc_type, exc_value, traceback):
 
442
        self.close_transport()
 
443
        return
 
444
 
 
445
    def open_transport(self):
 
446
        raise NotImplementedError
 
447
 
 
448
 
 
449
class JoyentMetadataSocketClient(JoyentMetadataClient):
 
450
    def __init__(self, socketpath):
 
451
        self.socketpath = socketpath
 
452
 
 
453
    def open_transport(self):
 
454
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
455
        sock.connect(self.socketpath)
 
456
        self.fp = sock.makefile('rwb')
 
457
 
 
458
    def exists(self):
 
459
        return os.path.exists(self.socketpath)
 
460
 
 
461
    def __repr__(self):
 
462
        return "%s(socketpath=%s)" % (self.__class__.__name__, self.socketpath)
 
463
 
 
464
 
 
465
class JoyentMetadataSerialClient(JoyentMetadataClient):
 
466
    def __init__(self, device, timeout=10, smartos_type=None):
 
467
        super(JoyentMetadataSerialClient, self).__init__(smartos_type)
 
468
        self.device = device
 
469
        self.timeout = timeout
 
470
 
 
471
    def exists(self):
 
472
        return os.path.exists(self.device)
 
473
 
 
474
    def open_transport(self):
 
475
        ser = serial.Serial(self.device, timeout=self.timeout)
 
476
        if not ser.isOpen():
 
477
            raise SystemError("Unable to open %s" % self.device)
 
478
        self.fp = ser
 
479
 
 
480
    def __repr__(self):
 
481
        return "%s(device=%s, timeout=%s)" % (
 
482
            self.__class__.__name__, self.device, self.timeout)
 
483
 
 
484
 
 
485
class JoyentMetadataLegacySerialClient(JoyentMetadataSerialClient):
 
486
    """V1 of the protocol was not safe for all values.
 
487
    Thus, we allowed the user to pass values in as base64 encoded.
 
488
    Users may still reasonably expect to be able to send base64 data
 
489
    and have it transparently decoded.  So even though the V2 format is
 
490
    now used, and is safe (using base64 itself), we keep legacy support.
 
491
 
 
492
    The way for a user to do this was:
 
493
      a.) specify 'base64_keys' key whose value is a comma delimited
 
494
          list of keys that were base64 encoded.
 
495
      b.) base64_all: string interpreted as a boolean that indicates
 
496
          if all keys are base64 encoded.
 
497
      c.) set a key named b64-<keyname> with a boolean indicating that
 
498
          <keyname> is base64 encoded."""
 
499
 
 
500
    def __init__(self, device, timeout=10, smartos_type=None):
 
501
        s = super(JoyentMetadataLegacySerialClient, self)
 
502
        s.__init__(device, timeout, smartos_type)
 
503
        self.base64_keys = None
 
504
        self.base64_all = None
 
505
 
 
506
    def _init_base64_keys(self, reset=False):
 
507
        if reset:
 
508
            self.base64_keys = None
 
509
            self.base64_all = None
 
510
 
 
511
        keys = None
 
512
        if self.base64_all is None:
 
513
            keys = self.list()
 
514
            if 'base64_all' in keys:
 
515
                self.base64_all = util.is_true(self._get("base64_all"))
 
516
            else:
 
517
                self.base64_all = False
 
518
 
 
519
        if self.base64_all:
 
520
            # short circuit if base64_all is true
 
521
            return
 
522
 
 
523
        if self.base64_keys is None:
 
524
            if keys is None:
 
525
                keys = self.list()
 
526
            b64_keys = set()
 
527
            if 'base64_keys' in keys:
 
528
                b64_keys = set(self._get("base64_keys").split(","))
 
529
 
 
530
            # now add any b64-<keyname> that has a true value
 
531
            for key in [k[3:] for k in keys if k.startswith("b64-")]:
 
532
                if util.is_true(self._get(key)):
 
533
                    b64_keys.add(key)
 
534
                else:
 
535
                    if key in b64_keys:
 
536
                        b64_keys.remove(key)
 
537
 
 
538
            self.base64_keys = b64_keys
 
539
 
 
540
    def _get(self, key, default=None, strip=False):
 
541
        return (super(JoyentMetadataLegacySerialClient, self).
 
542
                get(key, default=default, strip=strip))
 
543
 
 
544
    def is_b64_encoded(self, key, reset=False):
 
545
        if key in NO_BASE64_DECODE:
 
546
            return False
 
547
 
 
548
        self._init_base64_keys(reset=reset)
 
549
        if self.base64_all:
 
550
            return True
 
551
 
 
552
        return key in self.base64_keys
 
553
 
 
554
    def get(self, key, default=None, strip=False):
 
555
        mdefault = object()
 
556
        val = self._get(key, strip=False, default=mdefault)
 
557
        if val is mdefault:
 
558
            return default
 
559
 
 
560
        if self.is_b64_encoded(key):
 
561
            try:
 
562
                val = base64.b64decode(val.encode()).decode()
 
563
            # Bogus input produces different errors in Python 2 and 3
 
564
            except (TypeError, binascii.Error):
 
565
                LOG.warn("Failed base64 decoding key '%s': %s", key, val)
 
566
 
 
567
        if strip:
 
568
            val = val.strip()
 
569
 
 
570
        return val
 
571
 
 
572
 
 
573
def jmc_client_factory(
 
574
        smartos_type=None, metadata_sockfile=METADATA_SOCKFILE,
 
575
        serial_device=SERIAL_DEVICE, serial_timeout=SERIAL_TIMEOUT,
 
576
        uname_version=None):
 
577
 
 
578
    if smartos_type is None:
 
579
        smartos_type = get_smartos_environ(uname_version)
 
580
 
 
581
    if smartos_type is None:
467
582
        return None
 
583
    elif smartos_type == SMARTOS_ENV_KVM:
 
584
        return JoyentMetadataLegacySerialClient(
 
585
            device=serial_device, timeout=serial_timeout,
 
586
            smartos_type=smartos_type)
 
587
    elif smartos_type == SMARTOS_ENV_LX_BRAND:
 
588
        return JoyentMetadataSocketClient(socketpath=metadata_sockfile)
468
589
 
469
 
    return sys_type
 
590
    raise ValueError("Unknown value for smartos_type: %s" % smartos_type)
470
591
 
471
592
 
472
593
def write_boot_content(content, content_f, link=None, shebang=False,
522
643
                util.ensure_dir(os.path.dirname(link))
523
644
                os.symlink(content_f, link)
524
645
        except IOError as e:
525
 
            util.logexc(LOG, "failed establishing content link", e)
 
646
            util.logexc(LOG, "failed establishing content link: %s", e)
 
647
 
 
648
 
 
649
def get_smartos_environ(uname_version=None, product_name=None,
 
650
                        uname_arch=None):
 
651
    uname = os.uname()
 
652
    if uname_arch is None:
 
653
        uname_arch = uname[4]
 
654
 
 
655
    if uname_arch.startswith("arm") or uname_arch == "aarch64":
 
656
        return None
 
657
 
 
658
    # SDC LX-Brand Zones lack dmidecode (no /dev/mem) but
 
659
    # report 'BrandZ virtual linux' as the kernel version
 
660
    if uname_version is None:
 
661
        uname_version = uname[3]
 
662
    if uname_version.lower() == 'brandz virtual linux':
 
663
        return SMARTOS_ENV_LX_BRAND
 
664
 
 
665
    if product_name is None:
 
666
        system_type = util.read_dmi_data("system-product-name")
 
667
    else:
 
668
        system_type = product_name
 
669
 
 
670
    if system_type and 'smartdc' in system_type.lower():
 
671
        return SMARTOS_ENV_KVM
 
672
 
 
673
    return None
 
674
 
 
675
 
 
676
# Covert SMARTOS 'sdc:nics' data to network_config yaml
 
677
def convert_smartos_network_data(network_data=None):
 
678
    """Return a dictionary of network_config by parsing provided
 
679
       SMARTOS sdc:nics configuration data
 
680
 
 
681
    sdc:nics data is a dictionary of properties of a nic and the ip
 
682
    configuration desired.  Additional nic dictionaries are appended
 
683
    to the list.
 
684
 
 
685
    Converting the format is straightforward though it does include
 
686
    duplicate information as well as data which appears to be relevant
 
687
    to the hostOS rather than the guest.
 
688
 
 
689
    For each entry in the nics list returned from query sdc:nics, we
 
690
    create a type: physical entry, and extract the interface properties:
 
691
    'mac' -> 'mac_address', 'mtu', 'interface' -> 'name'.  The remaining
 
692
    keys are related to ip configuration.  For each ip in the 'ips' list
 
693
    we create a subnet entry under 'subnets' pairing the ip to a one in
 
694
    the 'gateways' list.
 
695
    """
 
696
 
 
697
    valid_keys = {
 
698
        'physical': [
 
699
            'mac_address',
 
700
            'mtu',
 
701
            'name',
 
702
            'params',
 
703
            'subnets',
 
704
            'type',
 
705
        ],
 
706
        'subnet': [
 
707
            'address',
 
708
            'broadcast',
 
709
            'dns_nameservers',
 
710
            'dns_search',
 
711
            'gateway',
 
712
            'metric',
 
713
            'netmask',
 
714
            'pointopoint',
 
715
            'routes',
 
716
            'scope',
 
717
            'type',
 
718
        ],
 
719
    }
 
720
 
 
721
    config = []
 
722
    for nic in network_data:
 
723
        cfg = {k: v for k, v in nic.items()
 
724
               if k in valid_keys['physical']}
 
725
        cfg.update({
 
726
            'type': 'physical',
 
727
            'name': nic['interface']})
 
728
        if 'mac' in nic:
 
729
            cfg.update({'mac_address': nic['mac']})
 
730
 
 
731
        subnets = []
 
732
        for ip, gw in zip(nic['ips'], nic['gateways']):
 
733
            subnet = {k: v for k, v in nic.items()
 
734
                      if k in valid_keys['subnet']}
 
735
            subnet.update({
 
736
                'type': 'static',
 
737
                'address': ip,
 
738
                'gateway': gw,
 
739
            })
 
740
            subnets.append(subnet)
 
741
        cfg.update({'subnets': subnets})
 
742
        config.append(cfg)
 
743
 
 
744
    return {'version': 1, 'config': config}
526
745
 
527
746
 
528
747
# Used to match classes to dependencies
529
748
datasources = [
530
 
    (DataSourceSmartOS, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
 
749
    (DataSourceSmartOS, (sources.DEP_FILESYSTEM, )),
531
750
]
532
751
 
533
752
 
534
753
# Return a list of data sources that match this set of dependencies
535
754
def get_datasource_list(depends):
536
755
    return sources.list_from_depends(depends, datasources)
 
756
 
 
757
 
 
758
if __name__ == "__main__":
 
759
    import sys
 
760
    jmc = jmc_client_factory()
 
761
    if jmc is None:
 
762
        print("Do not appear to be on smartos.")
 
763
        sys.exit(1)
 
764
    if len(sys.argv) == 1:
 
765
        keys = (list(SMARTOS_ATTRIB_JSON.keys()) +
 
766
                list(SMARTOS_ATTRIB_MAP.keys()))
 
767
    else:
 
768
        keys = sys.argv[1:]
 
769
 
 
770
    data = {}
 
771
    for key in keys:
 
772
        if key in SMARTOS_ATTRIB_JSON:
 
773
            keyname = SMARTOS_ATTRIB_JSON[key]
 
774
            data[key] = jmc.get_json(keyname)
 
775
        else:
 
776
            if key in SMARTOS_ATTRIB_MAP:
 
777
                keyname, strip = SMARTOS_ATTRIB_MAP[key]
 
778
            else:
 
779
                keyname, strip = (key, False)
 
780
            val = jmc.get(keyname, strip=strip)
 
781
            data[key] = jmc.get(keyname, strip=strip)
 
782
 
 
783
    print(json.dumps(data, indent=1))