Blame cloudinit/sources/helpers/openstack.py

Packit Service a04d08
# Copyright (C) 2012 Canonical Ltd.
Packit Service a04d08
# Copyright (C) 2012 Yahoo! Inc.
Packit Service a04d08
#
Packit Service a04d08
# Author: Scott Moser <scott.moser@canonical.com>
Packit Service a04d08
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
import abc
Packit Service a04d08
import base64
Packit Service a04d08
import copy
Packit Service a04d08
import functools
Packit Service a04d08
import os
Packit Service a04d08
Packit Service a04d08
from cloudinit import ec2_utils
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit import net
Packit Service a04d08
from cloudinit import sources
Packit Service 9bfd13
from cloudinit import subp
Packit Service a04d08
from cloudinit import url_helper
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
from cloudinit.sources import BrokenMetadata
Packit Service a04d08
Packit Service a04d08
# See https://docs.openstack.org/user-guide/cli-config-drive.html
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
FILES_V1 = {
Packit Service a04d08
    # Path <-> (metadata key name, translator function, default value)
Packit Service a04d08
    'etc/network/interfaces': ('network_config', lambda x: x, ''),
Packit Service a04d08
    'meta.js': ('meta_js', util.load_json, {}),
Packit Service a04d08
    "root/.ssh/authorized_keys": ('authorized_keys', lambda x: x, ''),
Packit Service a04d08
}
Packit Service a04d08
KEY_COPIES = (
Packit Service a04d08
    # Cloud-init metadata names <-> (metadata key, is required)
Packit Service a04d08
    ('local-hostname', 'hostname', False),
Packit Service a04d08
    ('instance-id', 'uuid', True),
Packit Service a04d08
)
Packit Service a04d08
Packit Service a04d08
# Versions and names taken from nova source nova/api/metadata/base.py
Packit Service a04d08
OS_LATEST = 'latest'
Packit Service a04d08
OS_FOLSOM = '2012-08-10'
Packit Service a04d08
OS_GRIZZLY = '2013-04-04'
Packit Service a04d08
OS_HAVANA = '2013-10-17'
Packit Service a04d08
OS_LIBERTY = '2015-10-15'
Packit Service a04d08
# NEWTON_ONE adds 'devices' to md (sriov-pf-passthrough-neutron-port-vlan)
Packit Service a04d08
OS_NEWTON_ONE = '2016-06-30'
Packit Service a04d08
# NEWTON_TWO adds vendor_data2.json (vendordata-reboot)
Packit Service a04d08
OS_NEWTON_TWO = '2016-10-06'
Packit Service a04d08
# OS_OCATA adds 'vif' field to devices (sriov-pf-passthrough-neutron-port-vlan)
Packit Service a04d08
OS_OCATA = '2017-02-22'
Packit Service a04d08
# OS_ROCKY adds a vf_trusted field to devices (sriov-trusted-vfs)
Packit Service a04d08
OS_ROCKY = '2018-08-27'
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# keep this in chronological order. new supported versions go at the end.
Packit Service a04d08
OS_VERSIONS = (
Packit Service a04d08
    OS_FOLSOM,
Packit Service a04d08
    OS_GRIZZLY,
Packit Service a04d08
    OS_HAVANA,
Packit Service a04d08
    OS_LIBERTY,
Packit Service a04d08
    OS_NEWTON_ONE,
Packit Service a04d08
    OS_NEWTON_TWO,
Packit Service a04d08
    OS_OCATA,
Packit Service a04d08
    OS_ROCKY,
Packit Service a04d08
)
Packit Service a04d08
Packit Service a04d08
KNOWN_PHYSICAL_TYPES = (
Packit Service a04d08
    None,
Packit Service a04d08
    'bgpovs',  # not present in OpenStack upstream but used on OVH cloud.
Packit Service a04d08
    'bridge',
Packit Service 9bfd13
    'cascading',  # not present in OpenStack upstream, used on OpenTelekomCloud
Packit Service a04d08
    'dvs',
Packit Service a04d08
    'ethernet',
Packit Service a04d08
    'hw_veb',
Packit Service a04d08
    'hyperv',
Packit Service a04d08
    'ovs',
Packit Service a04d08
    'phy',
Packit Service a04d08
    'tap',
Packit Service a04d08
    'vhostuser',
Packit Service a04d08
    'vif',
Packit Service a04d08
)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class NonReadable(IOError):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class SourceMixin(object):
Packit Service a04d08
    def _ec2_name_to_device(self, name):
Packit Service a04d08
        if not self.ec2_metadata:
Packit Service a04d08
            return None
Packit Service a04d08
        bdm = self.ec2_metadata.get('block-device-mapping', {})
Packit Service a04d08
        for (ent_name, device) in bdm.items():
Packit Service a04d08
            if name == ent_name:
Packit Service a04d08
                return device
Packit Service a04d08
        return None
Packit Service a04d08
Packit Service a04d08
    def get_public_ssh_keys(self):
Packit Service a04d08
        name = "public_keys"
Packit Service a04d08
        if self.version == 1:
Packit Service a04d08
            name = "public-keys"
Packit Service a04d08
        return sources.normalize_pubkey_data(self.metadata.get(name))
Packit Service a04d08
Packit Service a04d08
    def _os_name_to_device(self, name):
Packit Service a04d08
        device = None
Packit Service a04d08
        try:
Packit Service a04d08
            criteria = 'LABEL=%s' % (name)
Packit Service a04d08
            if name == 'swap':
Packit Service a04d08
                criteria = 'TYPE=%s' % (name)
Packit Service a04d08
            dev_entries = util.find_devs_with(criteria)
Packit Service a04d08
            if dev_entries:
Packit Service a04d08
                device = dev_entries[0]
Packit Service 9bfd13
        except subp.ProcessExecutionError:
Packit Service a04d08
            pass
Packit Service a04d08
        return device
Packit Service a04d08
Packit Service a04d08
    def _validate_device_name(self, device):
Packit Service a04d08
        if not device:
Packit Service a04d08
            return None
Packit Service a04d08
        if not device.startswith("/"):
Packit Service a04d08
            device = "/dev/%s" % device
Packit Service a04d08
        if os.path.exists(device):
Packit Service a04d08
            return device
Packit Service a04d08
        # Durn, try adjusting the mapping
Packit Service a04d08
        remapped = self._remap_device(os.path.basename(device))
Packit Service a04d08
        if remapped:
Packit Service a04d08
            LOG.debug("Remapped device name %s => %s", device, remapped)
Packit Service a04d08
            return remapped
Packit Service a04d08
        return None
Packit Service a04d08
Packit Service a04d08
    def device_name_to_device(self, name):
Packit Service a04d08
        # Translate a 'name' to a 'physical' device
Packit Service a04d08
        if not name:
Packit Service a04d08
            return None
Packit Service a04d08
        # Try the ec2 mapping first
Packit Service a04d08
        names = [name]
Packit Service a04d08
        if name == 'root':
Packit Service a04d08
            names.insert(0, 'ami')
Packit Service a04d08
        if name == 'ami':
Packit Service a04d08
            names.append('root')
Packit Service a04d08
        device = None
Packit Service a04d08
        LOG.debug("Using ec2 style lookup to find device %s", names)
Packit Service a04d08
        for n in names:
Packit Service a04d08
            device = self._ec2_name_to_device(n)
Packit Service a04d08
            device = self._validate_device_name(device)
Packit Service a04d08
            if device:
Packit Service a04d08
                break
Packit Service a04d08
        # Try the openstack way second
Packit Service a04d08
        if not device:
Packit Service a04d08
            LOG.debug("Using openstack style lookup to find device %s", names)
Packit Service a04d08
            for n in names:
Packit Service a04d08
                device = self._os_name_to_device(n)
Packit Service a04d08
                device = self._validate_device_name(device)
Packit Service a04d08
                if device:
Packit Service a04d08
                    break
Packit Service a04d08
        # Ok give up...
Packit Service a04d08
        if not device:
Packit Service a04d08
            return None
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.debug("Mapped %s to device %s", name, device)
Packit Service a04d08
            return device
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
class BaseReader(metaclass=abc.ABCMeta):
Packit Service a04d08
Packit Service a04d08
    def __init__(self, base_path):
Packit Service a04d08
        self.base_path = base_path
Packit Service a04d08
Packit Service a04d08
    @abc.abstractmethod
Packit Service a04d08
    def _path_join(self, base, *add_ons):
Packit Service a04d08
        pass
Packit Service a04d08
Packit Service a04d08
    @abc.abstractmethod
Packit Service a04d08
    def _path_read(self, path, decode=False):
Packit Service a04d08
        pass
Packit Service a04d08
Packit Service a04d08
    @abc.abstractmethod
Packit Service a04d08
    def _fetch_available_versions(self):
Packit Service a04d08
        pass
Packit Service a04d08
Packit Service a04d08
    @abc.abstractmethod
Packit Service a04d08
    def _read_ec2_metadata(self):
Packit Service a04d08
        pass
Packit Service a04d08
Packit Service a04d08
    def _find_working_version(self):
Packit Service a04d08
        try:
Packit Service a04d08
            versions_available = self._fetch_available_versions()
Packit Service a04d08
        except Exception as e:
Packit Service a04d08
            LOG.debug("Unable to read openstack versions from %s due to: %s",
Packit Service a04d08
                      self.base_path, e)
Packit Service a04d08
            versions_available = []
Packit Service a04d08
Packit Service a04d08
        # openstack.OS_VERSIONS is stored in chronological order, so
Packit Service a04d08
        # reverse it to check newest first.
Packit Service a04d08
        supported = [v for v in reversed(list(OS_VERSIONS))]
Packit Service a04d08
        selected_version = OS_LATEST
Packit Service a04d08
Packit Service a04d08
        for potential_version in supported:
Packit Service a04d08
            if potential_version not in versions_available:
Packit Service a04d08
                continue
Packit Service a04d08
            selected_version = potential_version
Packit Service a04d08
            break
Packit Service a04d08
Packit Service a04d08
        LOG.debug("Selected version '%s' from %s", selected_version,
Packit Service a04d08
                  versions_available)
Packit Service a04d08
        return selected_version
Packit Service a04d08
Packit Service a04d08
    def _read_content_path(self, item, decode=False):
Packit Service a04d08
        path = item.get('content_path', '').lstrip("/")
Packit Service a04d08
        path_pieces = path.split("/")
Packit Service a04d08
        valid_pieces = [p for p in path_pieces if len(p)]
Packit Service a04d08
        if not valid_pieces:
Packit Service a04d08
            raise BrokenMetadata("Item %s has no valid content path" % (item))
Packit Service a04d08
        path = self._path_join(self.base_path, "openstack", *path_pieces)
Packit Service a04d08
        return self._path_read(path, decode=decode)
Packit Service a04d08
Packit Service a04d08
    def read_v2(self):
Packit Service a04d08
        """Reads a version 2 formatted location.
Packit Service a04d08
Packit Service a04d08
        Return a dict with metadata, userdata, ec2-metadata, dsmode,
Packit Service a04d08
        network_config, files and version (2).
Packit Service a04d08
Packit Service a04d08
        If not a valid location, raise a NonReadable exception.
Packit Service a04d08
        """
Packit Service a04d08
Packit Service a04d08
        load_json_anytype = functools.partial(
Packit Service 9bfd13
            util.load_json, root_types=(dict, list, str))
Packit Service a04d08
Packit Service a04d08
        def datafiles(version):
Packit Service a04d08
            files = {}
Packit Service a04d08
            files['metadata'] = (
Packit Service a04d08
                # File path to read
Packit Service a04d08
                self._path_join("openstack", version, 'meta_data.json'),
Packit Service a04d08
                # Is it required?
Packit Service a04d08
                True,
Packit Service a04d08
                # Translator function (applied after loading)
Packit Service a04d08
                util.load_json,
Packit Service a04d08
            )
Packit Service a04d08
            files['userdata'] = (
Packit Service a04d08
                self._path_join("openstack", version, 'user_data'),
Packit Service a04d08
                False,
Packit Service a04d08
                lambda x: x,
Packit Service a04d08
            )
Packit Service a04d08
            files['vendordata'] = (
Packit Service a04d08
                self._path_join("openstack", version, 'vendor_data.json'),
Packit Service a04d08
                False,
Packit Service a04d08
                load_json_anytype,
Packit Service a04d08
            )
Packit Service a04d08
            files['networkdata'] = (
Packit Service a04d08
                self._path_join("openstack", version, 'network_data.json'),
Packit Service a04d08
                False,
Packit Service a04d08
                load_json_anytype,
Packit Service a04d08
            )
Packit Service a04d08
            return files
Packit Service a04d08
Packit Service a04d08
        results = {
Packit Service a04d08
            'userdata': '',
Packit Service a04d08
            'version': 2,
Packit Service a04d08
        }
Packit Service a04d08
        data = datafiles(self._find_working_version())
Packit Service a04d08
        for (name, (path, required, translator)) in data.items():
Packit Service a04d08
            path = self._path_join(self.base_path, path)
Packit Service a04d08
            data = None
Packit Service a04d08
            found = False
Packit Service a04d08
            try:
Packit Service a04d08
                data = self._path_read(path)
Packit Service a04d08
            except IOError as e:
Packit Service a04d08
                if not required:
Packit Service a04d08
                    LOG.debug("Failed reading optional path %s due"
Packit Service a04d08
                              " to: %s", path, e)
Packit Service a04d08
                else:
Packit Service a04d08
                    LOG.debug("Failed reading mandatory path %s due"
Packit Service a04d08
                              " to: %s", path, e)
Packit Service a04d08
            else:
Packit Service a04d08
                found = True
Packit Service a04d08
            if required and not found:
Packit Service a04d08
                raise NonReadable("Missing mandatory path: %s" % path)
Packit Service a04d08
            if found and translator:
Packit Service a04d08
                try:
Packit Service a04d08
                    data = translator(data)
Packit Service a04d08
                except Exception as e:
Packit Service 9bfd13
                    raise BrokenMetadata(
Packit Service 9bfd13
                        "Failed to process path %s: %s" % (path, e)
Packit Service 9bfd13
                    ) from e
Packit Service a04d08
            if found:
Packit Service a04d08
                results[name] = data
Packit Service a04d08
Packit Service a04d08
        metadata = results['metadata']
Packit Service a04d08
        if 'random_seed' in metadata:
Packit Service a04d08
            random_seed = metadata['random_seed']
Packit Service a04d08
            try:
Packit Service a04d08
                metadata['random_seed'] = base64.b64decode(random_seed)
Packit Service a04d08
            except (ValueError, TypeError) as e:
Packit Service 9bfd13
                raise BrokenMetadata(
Packit Service 9bfd13
                    "Badly formatted metadata random_seed entry: %s" % e
Packit Service 9bfd13
                ) from e
Packit Service a04d08
Packit Service a04d08
        # load any files that were provided
Packit Service a04d08
        files = {}
Packit Service a04d08
        metadata_files = metadata.get('files', [])
Packit Service a04d08
        for item in metadata_files:
Packit Service a04d08
            if 'path' not in item:
Packit Service a04d08
                continue
Packit Service a04d08
            path = item['path']
Packit Service a04d08
            try:
Packit Service a04d08
                files[path] = self._read_content_path(item)
Packit Service a04d08
            except Exception as e:
Packit Service 9bfd13
                raise BrokenMetadata(
Packit Service 9bfd13
                    "Failed to read provided file %s: %s" % (path, e)
Packit Service 9bfd13
                ) from e
Packit Service a04d08
        results['files'] = files
Packit Service a04d08
Packit Service a04d08
        # The 'network_config' item in metadata is a content pointer
Packit Service a04d08
        # to the network config that should be applied. It is just a
Packit Service a04d08
        # ubuntu/debian '/etc/network/interfaces' file.
Packit Service a04d08
        net_item = metadata.get("network_config", None)
Packit Service a04d08
        if net_item:
Packit Service a04d08
            try:
Packit Service a04d08
                content = self._read_content_path(net_item, decode=True)
Packit Service a04d08
                results['network_config'] = content
Packit Service a04d08
            except IOError as e:
Packit Service 9bfd13
                raise BrokenMetadata(
Packit Service 9bfd13
                    "Failed to read network configuration: %s" % (e)
Packit Service 9bfd13
                ) from e
Packit Service a04d08
Packit Service a04d08
        # To openstack, user can specify meta ('nova boot --meta=key=value')
Packit Service a04d08
        # and those will appear under metadata['meta'].
Packit Service a04d08
        # if they specify 'dsmode' they're indicating the mode that they intend
Packit Service a04d08
        # for this datasource to operate in.
Packit Service a04d08
        try:
Packit Service a04d08
            results['dsmode'] = metadata['meta']['dsmode']
Packit Service a04d08
        except KeyError:
Packit Service a04d08
            pass
Packit Service a04d08
Packit Service a04d08
        # Read any ec2-metadata (if applicable)
Packit Service a04d08
        results['ec2-metadata'] = self._read_ec2_metadata()
Packit Service a04d08
Packit Service a04d08
        # Perform some misc. metadata key renames...
Packit Service a04d08
        for (target_key, source_key, is_required) in KEY_COPIES:
Packit Service a04d08
            if is_required and source_key not in metadata:
Packit Service a04d08
                raise BrokenMetadata("No '%s' entry in metadata" % source_key)
Packit Service a04d08
            if source_key in metadata:
Packit Service a04d08
                metadata[target_key] = metadata.get(source_key)
Packit Service a04d08
        return results
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class ConfigDriveReader(BaseReader):
Packit Service a04d08
    def __init__(self, base_path):
Packit Service a04d08
        super(ConfigDriveReader, self).__init__(base_path)
Packit Service a04d08
        self._versions = None
Packit Service a04d08
Packit Service a04d08
    def _path_join(self, base, *add_ons):
Packit Service a04d08
        components = [base] + list(add_ons)
Packit Service a04d08
        return os.path.join(*components)
Packit Service a04d08
Packit Service a04d08
    def _path_read(self, path, decode=False):
Packit Service a04d08
        return util.load_file(path, decode=decode)
Packit Service a04d08
Packit Service a04d08
    def _fetch_available_versions(self):
Packit Service a04d08
        if self._versions is None:
Packit Service a04d08
            path = self._path_join(self.base_path, 'openstack')
Packit Service a04d08
            found = [d for d in os.listdir(path)
Packit Service a04d08
                     if os.path.isdir(os.path.join(path))]
Packit Service a04d08
            self._versions = sorted(found)
Packit Service a04d08
        return self._versions
Packit Service a04d08
Packit Service a04d08
    def _read_ec2_metadata(self):
Packit Service a04d08
        path = self._path_join(self.base_path,
Packit Service a04d08
                               'ec2', 'latest', 'meta-data.json')
Packit Service a04d08
        if not os.path.exists(path):
Packit Service a04d08
            return {}
Packit Service a04d08
        else:
Packit Service a04d08
            try:
Packit Service a04d08
                return util.load_json(self._path_read(path))
Packit Service a04d08
            except Exception as e:
Packit Service 9bfd13
                raise BrokenMetadata(
Packit Service 9bfd13
                    "Failed to process path %s: %s" % (path, e)
Packit Service 9bfd13
                ) from e
Packit Service a04d08
Packit Service a04d08
    def read_v1(self):
Packit Service a04d08
        """Reads a version 1 formatted location.
Packit Service a04d08
Packit Service a04d08
        Return a dict with metadata, userdata, dsmode, files and version (1).
Packit Service a04d08
Packit Service a04d08
        If not a valid path, raise a NonReadable exception.
Packit Service a04d08
        """
Packit Service a04d08
Packit Service a04d08
        found = {}
Packit Service a04d08
        for name in FILES_V1.keys():
Packit Service a04d08
            path = self._path_join(self.base_path, name)
Packit Service a04d08
            if os.path.exists(path):
Packit Service a04d08
                found[name] = path
Packit Service a04d08
        if len(found) == 0:
Packit Service a04d08
            raise NonReadable("%s: no files found" % (self.base_path))
Packit Service a04d08
Packit Service a04d08
        md = {}
Packit Service a04d08
        for (name, (key, translator, default)) in FILES_V1.items():
Packit Service a04d08
            if name in found:
Packit Service a04d08
                path = found[name]
Packit Service a04d08
                try:
Packit Service a04d08
                    contents = self._path_read(path)
Packit Service 9bfd13
                except IOError as e:
Packit Service 9bfd13
                    raise BrokenMetadata("Failed to read: %s" % path) from e
Packit Service a04d08
                try:
Packit Service 9bfd13
                    # Disable not-callable pylint check; pylint isn't able to
Packit Service 9bfd13
                    # determine that every member of FILES_V1 has a callable in
Packit Service 9bfd13
                    # the appropriate position
Packit Service 9bfd13
                    md[key] = translator(contents)  # pylint: disable=E1102
Packit Service a04d08
                except Exception as e:
Packit Service 9bfd13
                    raise BrokenMetadata(
Packit Service 9bfd13
                        "Failed to process path %s: %s" % (path, e)
Packit Service 9bfd13
                    ) from e
Packit Service a04d08
            else:
Packit Service a04d08
                md[key] = copy.deepcopy(default)
Packit Service a04d08
Packit Service a04d08
        keydata = md['authorized_keys']
Packit Service a04d08
        meta_js = md['meta_js']
Packit Service a04d08
Packit Service a04d08
        # keydata in meta_js is preferred over "injected"
Packit Service a04d08
        keydata = meta_js.get('public-keys', keydata)
Packit Service a04d08
        if keydata:
Packit Service a04d08
            lines = keydata.splitlines()
Packit Service 9bfd13
            md['public-keys'] = [
Packit Service 9bfd13
                line
Packit Service 9bfd13
                for line in lines
Packit Service 9bfd13
                if len(line) and not line.startswith("#")
Packit Service 9bfd13
            ]
Packit Service a04d08
Packit Service a04d08
        # config-drive-v1 has no way for openstack to provide the instance-id
Packit Service a04d08
        # so we copy that into metadata from the user input
Packit Service a04d08
        if 'instance-id' in meta_js:
Packit Service a04d08
            md['instance-id'] = meta_js['instance-id']
Packit Service a04d08
Packit Service a04d08
        results = {
Packit Service a04d08
            'version': 1,
Packit Service a04d08
            'metadata': md,
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
        # allow the user to specify 'dsmode' in a meta tag
Packit Service a04d08
        if 'dsmode' in meta_js:
Packit Service a04d08
            results['dsmode'] = meta_js['dsmode']
Packit Service a04d08
Packit Service a04d08
        # config-drive-v1 has no way of specifying user-data, so the user has
Packit Service a04d08
        # to cheat and stuff it in a meta tag also.
Packit Service a04d08
        results['userdata'] = meta_js.get('user-data', '')
Packit Service a04d08
Packit Service a04d08
        # this implementation does not support files other than
Packit Service a04d08
        # network/interfaces and authorized_keys...
Packit Service a04d08
        results['files'] = {}
Packit Service a04d08
Packit Service a04d08
        return results
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class MetadataReader(BaseReader):
Packit Service a04d08
    def __init__(self, base_url, ssl_details=None, timeout=5, retries=5):
Packit Service a04d08
        super(MetadataReader, self).__init__(base_url)
Packit Service a04d08
        self.ssl_details = ssl_details
Packit Service a04d08
        self.timeout = float(timeout)
Packit Service a04d08
        self.retries = int(retries)
Packit Service a04d08
        self._versions = None
Packit Service a04d08
Packit Service a04d08
    def _fetch_available_versions(self):
Packit Service a04d08
        # <baseurl>/openstack/ returns a newline separated list of versions
Packit Service a04d08
        if self._versions is not None:
Packit Service a04d08
            return self._versions
Packit Service a04d08
        found = []
Packit Service a04d08
        version_path = self._path_join(self.base_path, "openstack")
Packit Service a04d08
        content = self._path_read(version_path, decode=True)
Packit Service a04d08
        for line in content.splitlines():
Packit Service a04d08
            line = line.strip()
Packit Service a04d08
            if not line:
Packit Service a04d08
                continue
Packit Service a04d08
            found.append(line)
Packit Service a04d08
        self._versions = found
Packit Service a04d08
        return self._versions
Packit Service a04d08
Packit Service a04d08
    def _path_read(self, path, decode=False):
Packit Service a04d08
Packit Service a04d08
        def should_retry_cb(_request_args, cause):
Packit Service a04d08
            try:
Packit Service a04d08
                code = int(cause.code)
Packit Service a04d08
                if code >= 400:
Packit Service a04d08
                    return False
Packit Service a04d08
            except (TypeError, ValueError):
Packit Service a04d08
                # Older versions of requests didn't have a code.
Packit Service a04d08
                pass
Packit Service a04d08
            return True
Packit Service a04d08
Packit Service a04d08
        response = url_helper.readurl(path,
Packit Service a04d08
                                      retries=self.retries,
Packit Service a04d08
                                      ssl_details=self.ssl_details,
Packit Service a04d08
                                      timeout=self.timeout,
Packit Service a04d08
                                      exception_cb=should_retry_cb)
Packit Service a04d08
        if decode:
Packit Service a04d08
            return response.contents.decode()
Packit Service a04d08
        else:
Packit Service a04d08
            return response.contents
Packit Service a04d08
Packit Service a04d08
    def _path_join(self, base, *add_ons):
Packit Service a04d08
        return url_helper.combine_url(base, *add_ons)
Packit Service a04d08
Packit Service a04d08
    def _read_ec2_metadata(self):
Packit Service a04d08
        return ec2_utils.get_instance_metadata(ssl_details=self.ssl_details,
Packit Service a04d08
                                               timeout=self.timeout,
Packit Service a04d08
                                               retries=self.retries)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Convert OpenStack ConfigDrive NetworkData json to network_config yaml
Packit Service a04d08
def convert_net_json(network_json=None, known_macs=None):
Packit Service a04d08
    """Return a dictionary of network_config by parsing provided
Packit Service a04d08
       OpenStack ConfigDrive NetworkData json format
Packit Service a04d08
Packit Service a04d08
    OpenStack network_data.json provides a 3 element dictionary
Packit Service a04d08
      - "links" (links are network devices, physical or virtual)
Packit Service a04d08
      - "networks" (networks are ip network configurations for one or more
Packit Service a04d08
                    links)
Packit Service a04d08
      -  services (non-ip services, like dns)
Packit Service a04d08
Packit Service a04d08
    networks and links are combined via network items referencing specific
Packit Service a04d08
    links via a 'link_id' which maps to a links 'id' field.
Packit Service a04d08
Packit Service a04d08
    To convert this format to network_config yaml, we first iterate over the
Packit Service a04d08
    links and then walk the network list to determine if any of the networks
Packit Service a04d08
    utilize the current link; if so we generate a subnet entry for the device
Packit Service a04d08
Packit Service a04d08
    We also need to map network_data.json fields to network_config fields. For
Packit Service a04d08
    example, the network_data links 'id' field is equivalent to network_config
Packit Service a04d08
    'name' field for devices.  We apply more of this mapping to the various
Packit Service a04d08
    link types that we encounter.
Packit Service a04d08
Packit Service a04d08
    There are additional fields that are populated in the network_data.json
Packit Service a04d08
    from OpenStack that are not relevant to network_config yaml, so we
Packit Service a04d08
    enumerate a dictionary of valid keys for network_yaml and apply filtering
Packit Service a04d08
    to drop these superflous keys from the network_config yaml.
Packit Service a04d08
    """
Packit Service a04d08
    if network_json is None:
Packit Service a04d08
        return None
Packit Service a04d08
Packit Service a04d08
    # dict of network_config key for filtering network_json
Packit Service a04d08
    valid_keys = {
Packit Service a04d08
        'physical': [
Packit Service a04d08
            'name',
Packit Service a04d08
            'type',
Packit Service a04d08
            'mac_address',
Packit Service a04d08
            'subnets',
Packit Service a04d08
            'params',
Packit Service a04d08
            'mtu',
Packit Service a04d08
        ],
Packit Service a04d08
        'subnet': [
Packit Service a04d08
            'type',
Packit Service a04d08
            'address',
Packit Service a04d08
            'netmask',
Packit Service a04d08
            'broadcast',
Packit Service a04d08
            'metric',
Packit Service a04d08
            'gateway',
Packit Service a04d08
            'pointopoint',
Packit Service a04d08
            'scope',
Packit Service a04d08
            'dns_nameservers',
Packit Service a04d08
            'dns_search',
Packit Service a04d08
            'routes',
Packit Service a04d08
        ],
Packit Service a04d08
    }
Packit Service a04d08
Packit Service a04d08
    links = network_json.get('links', [])
Packit Service a04d08
    networks = network_json.get('networks', [])
Packit Service a04d08
    services = network_json.get('services', [])
Packit Service a04d08
Packit Service a04d08
    link_updates = []
Packit Service a04d08
    link_id_info = {}
Packit Service a04d08
    bond_name_fmt = "bond%d"
Packit Service a04d08
    bond_number = 0
Packit Service a04d08
    config = []
Packit Service a04d08
    for link in links:
Packit Service a04d08
        subnets = []
Packit Service a04d08
        cfg = dict((k, v) for k, v in link.items()
Packit Service a04d08
                   if k in valid_keys['physical'])
Packit Service a04d08
        # 'name' is not in openstack spec yet, but we will support it if it is
Packit Service a04d08
        # present.  The 'id' in the spec is currently implemented as the host
Packit Service a04d08
        # nic's name, meaning something like 'tap-adfasdffd'.  We do not want
Packit Service a04d08
        # to name guest devices with such ugly names.
Packit Service a04d08
        if 'name' in link:
Packit Service a04d08
            cfg['name'] = link['name']
Packit Service a04d08
Packit Service a04d08
        link_mac_addr = None
Packit Service a04d08
        if link.get('ethernet_mac_address'):
Packit Service a04d08
            link_mac_addr = link.get('ethernet_mac_address').lower()
Packit Service a04d08
            link_id_info[link['id']] = link_mac_addr
Packit Service a04d08
Packit Service a04d08
        curinfo = {'name': cfg.get('name'), 'mac': link_mac_addr,
Packit Service a04d08
                   'id': link['id'], 'type': link['type']}
Packit Service a04d08
Packit Service a04d08
        for network in [n for n in networks
Packit Service a04d08
                        if n['link'] == link['id']]:
Packit Service a04d08
            subnet = dict((k, v) for k, v in network.items()
Packit Service a04d08
                          if k in valid_keys['subnet'])
Packit Service a04d08
Packit Service a04d08
            if network['type'] == 'ipv4_dhcp':
Packit Service a04d08
                subnet.update({'type': 'dhcp4'})
Packit Service a04d08
            elif network['type'] == 'ipv6_dhcp':
Packit Service a04d08
                subnet.update({'type': 'dhcp6'})
Packit Service a04d08
            elif network['type'] in ['ipv6_slaac', 'ipv6_dhcpv6-stateless',
Packit Service a04d08
                                     'ipv6_dhcpv6-stateful']:
Packit Service a04d08
                subnet.update({'type': network['type']})
Packit Service be4044
            elif network['type'] in ['ipv4', 'ipv6']:
Packit Service a04d08
                subnet.update({
Packit Service a04d08
                    'type': 'static',
Packit Service a04d08
                    'address': network.get('ip_address'),
Packit Service a04d08
                })
Packit Service a04d08
Packit Service a04d08
            # Enable accept_ra for stateful and legacy ipv6_dhcp types
Packit Service a04d08
            if network['type'] in ['ipv6_dhcpv6-stateful', 'ipv6_dhcp']:
Packit Service a04d08
                cfg.update({'accept-ra': True})
Packit Service a04d08
Packit Service a04d08
            if network['type'] == 'ipv4':
Packit Service a04d08
                subnet['ipv4'] = True
Packit Service a04d08
            if network['type'] == 'ipv6':
Packit Service a04d08
                subnet['ipv6'] = True
Packit Service a04d08
            subnets.append(subnet)
Packit Service a04d08
        cfg.update({'subnets': subnets})
Packit Service a04d08
        if link['type'] in ['bond']:
Packit Service a04d08
            params = {}
Packit Service a04d08
            if link_mac_addr:
Packit Service a04d08
                params['mac_address'] = link_mac_addr
Packit Service a04d08
            for k, v in link.items():
Packit Service a04d08
                if k == 'bond_links':
Packit Service a04d08
                    continue
Packit Service a04d08
                elif k.startswith('bond'):
Packit Service a04d08
                    params.update({k: v})
Packit Service a04d08
Packit Service a04d08
            # openstack does not provide a name for the bond.
Packit Service a04d08
            # they do provide an 'id', but that is possibly non-sensical.
Packit Service a04d08
            # so we just create our own name.
Packit Service a04d08
            link_name = bond_name_fmt % bond_number
Packit Service a04d08
            bond_number += 1
Packit Service a04d08
Packit Service a04d08
            # bond_links reference links by their id, but we need to add
Packit Service a04d08
            # to the network config by their nic name.
Packit Service a04d08
            # store that in bond_links_needed, and update these later.
Packit Service a04d08
            link_updates.append(
Packit Service a04d08
                (cfg, 'bond_interfaces', '%s',
Packit Service a04d08
                 copy.deepcopy(link['bond_links']))
Packit Service a04d08
            )
Packit Service a04d08
            cfg.update({'params': params, 'name': link_name})
Packit Service a04d08
Packit Service a04d08
            curinfo['name'] = link_name
Packit Service a04d08
        elif link['type'] in ['vlan']:
Packit Service a04d08
            name = "%s.%s" % (link['vlan_link'], link['vlan_id'])
Packit Service a04d08
            cfg.update({
Packit Service a04d08
                'name': name,
Packit Service a04d08
                'vlan_id': link['vlan_id'],
Packit Service a04d08
                'mac_address': link['vlan_mac_address'],
Packit Service a04d08
            })
Packit Service a04d08
            link_updates.append((cfg, 'vlan_link', '%s', link['vlan_link']))
Packit Service a04d08
            link_updates.append((cfg, 'name', "%%s.%s" % link['vlan_id'],
Packit Service a04d08
                                 link['vlan_link']))
Packit Service a04d08
            curinfo.update({'mac': link['vlan_mac_address'],
Packit Service a04d08
                            'name': name})
Packit Service a04d08
        else:
Packit Service a04d08
            if link['type'] not in KNOWN_PHYSICAL_TYPES:
Packit Service a04d08
                LOG.warning('Unknown network_data link type (%s); treating as'
Packit Service a04d08
                            ' physical', link['type'])
Packit Service a04d08
            cfg.update({'type': 'physical', 'mac_address': link_mac_addr})
Packit Service a04d08
Packit Service a04d08
        config.append(cfg)
Packit Service a04d08
        link_id_info[curinfo['id']] = curinfo
Packit Service a04d08
Packit Service a04d08
    need_names = [d for d in config
Packit Service a04d08
                  if d.get('type') == 'physical' and 'name' not in d]
Packit Service a04d08
Packit Service a04d08
    if need_names or link_updates:
Packit Service a04d08
        if known_macs is None:
Packit Service a04d08
            known_macs = net.get_interfaces_by_mac()
Packit Service a04d08
Packit Service a04d08
        # go through and fill out the link_id_info with names
Packit Service a04d08
        for _link_id, info in link_id_info.items():
Packit Service a04d08
            if info.get('name'):
Packit Service a04d08
                continue
Packit Service a04d08
            if info.get('mac') in known_macs:
Packit Service a04d08
                info['name'] = known_macs[info['mac']]
Packit Service a04d08
Packit Service a04d08
        for d in need_names:
Packit Service a04d08
            mac = d.get('mac_address')
Packit Service a04d08
            if not mac:
Packit Service a04d08
                raise ValueError("No mac_address or name entry for %s" % d)
Packit Service a04d08
            if mac not in known_macs:
Packit Service a04d08
                raise ValueError("Unable to find a system nic for %s" % d)
Packit Service a04d08
            d['name'] = known_macs[mac]
Packit Service a04d08
Packit Service 9bfd13
        for cfg, key, fmt, targets in link_updates:
Packit Service 9bfd13
            if isinstance(targets, (list, tuple)):
Packit Service 9bfd13
                cfg[key] = [
Packit Service 9bfd13
                    fmt % link_id_info[target]['name'] for target in targets
Packit Service 9bfd13
                ]
Packit Service a04d08
            else:
Packit Service 9bfd13
                cfg[key] = fmt % link_id_info[targets]['name']
Packit Service a04d08
Packit Service a04d08
    # Infiniband interfaces may be referenced in network_data.json by a 6 byte
Packit Service a04d08
    # Ethernet MAC-style address, and we use that address to look up the
Packit Service a04d08
    # interface name above. Now ensure that the hardware address is set to the
Packit Service a04d08
    # full 20 byte address.
Packit Service a04d08
    ib_known_hwaddrs = net.get_ib_hwaddrs_by_interface()
Packit Service a04d08
    if ib_known_hwaddrs:
Packit Service a04d08
        for cfg in config:
Packit Service a04d08
            if cfg['name'] in ib_known_hwaddrs:
Packit Service a04d08
                cfg['mac_address'] = ib_known_hwaddrs[cfg['name']]
Packit Service a04d08
                cfg['type'] = 'infiniband'
Packit Service a04d08
Packit Service a04d08
    for service in services:
Packit Service a04d08
        cfg = service
Packit Service a04d08
        cfg.update({'type': 'nameserver'})
Packit Service a04d08
        config.append(cfg)
Packit Service a04d08
Packit Service a04d08
    return {'version': 1, 'config': config}
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab