Blame cloudinit/sources/DataSourceOpenStack.py

Packit Service a04d08
# Copyright (C) 2014 Yahoo! Inc.
Packit Service a04d08
#
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 time
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError
Packit Service a04d08
from cloudinit import sources
Packit Service a04d08
from cloudinit import url_helper
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
from cloudinit.sources.helpers import openstack
Packit Service a04d08
from cloudinit.sources import DataSourceOracle as oracle
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
# Various defaults/constants...
Packit Service a04d08
DEF_MD_URL = "http://169.254.169.254"
Packit Service a04d08
DEFAULT_IID = "iid-dsopenstack"
Packit Service a04d08
DEFAULT_METADATA = {
Packit Service a04d08
    "instance-id": DEFAULT_IID,
Packit Service a04d08
}
Packit Service a04d08
Packit Service a04d08
# OpenStack DMI constants
Packit Service a04d08
DMI_PRODUCT_NOVA = 'OpenStack Nova'
Packit Service a04d08
DMI_PRODUCT_COMPUTE = 'OpenStack Compute'
Packit Service a04d08
VALID_DMI_PRODUCT_NAMES = [DMI_PRODUCT_NOVA, DMI_PRODUCT_COMPUTE]
Packit Service a04d08
DMI_ASSET_TAG_OPENTELEKOM = 'OpenTelekomCloud'
Packit Service 11b429
VALID_DMI_ASSET_TAGS = [DMI_ASSET_TAG_OPENTELEKOM]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
Packit Service a04d08
Packit Service a04d08
    dsname = "OpenStack"
Packit Service a04d08
Packit Service a04d08
    _network_config = sources.UNSET  # Used to cache calculated network cfg v1
Packit Service a04d08
Packit Service a04d08
    # Whether we want to get network configuration from the metadata service.
Packit Service a04d08
    perform_dhcp_setup = False
Packit Service a04d08
Packit Service a04d08
    def __init__(self, sys_cfg, distro, paths):
Packit Service a04d08
        super(DataSourceOpenStack, self).__init__(sys_cfg, distro, paths)
Packit Service a04d08
        self.metadata_address = None
Packit Service a04d08
        self.ssl_details = util.fetch_ssl_details(self.paths)
Packit Service a04d08
        self.version = None
Packit Service a04d08
        self.files = {}
Packit Service a04d08
        self.ec2_metadata = sources.UNSET
Packit Service a04d08
        self.network_json = sources.UNSET
Packit Service a04d08
Packit Service a04d08
    def __str__(self):
Packit Service a04d08
        root = sources.DataSource.__str__(self)
Packit Service a04d08
        mstr = "%s [%s,ver=%s]" % (root, self.dsmode, self.version)
Packit Service a04d08
        return mstr
Packit Service a04d08
Packit Service a04d08
    def wait_for_metadata_service(self):
Packit Service a04d08
        urls = self.ds_cfg.get("metadata_urls", [DEF_MD_URL])
Packit Service a04d08
        filtered = [x for x in urls if util.is_resolvable_url(x)]
Packit Service a04d08
        if set(filtered) != set(urls):
Packit Service a04d08
            LOG.debug("Removed the following from metadata urls: %s",
Packit Service a04d08
                      list((set(urls) - set(filtered))))
Packit Service a04d08
        if len(filtered):
Packit Service a04d08
            urls = filtered
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.warning("Empty metadata url list! using default list")
Packit Service a04d08
            urls = [DEF_MD_URL]
Packit Service a04d08
Packit Service a04d08
        md_urls = []
Packit Service a04d08
        url2base = {}
Packit Service a04d08
        for url in urls:
Packit Service a04d08
            md_url = url_helper.combine_url(url, 'openstack')
Packit Service a04d08
            md_urls.append(md_url)
Packit Service a04d08
            url2base[md_url] = url
Packit Service a04d08
Packit Service a04d08
        url_params = self.get_url_params()
Packit Service a04d08
        start_time = time.time()
Packit Service a04d08
        avail_url, _response = url_helper.wait_for_url(
Packit Service a04d08
            urls=md_urls, max_wait=url_params.max_wait_seconds,
Packit Service a04d08
            timeout=url_params.timeout_seconds)
Packit Service a04d08
        if avail_url:
Packit Service a04d08
            LOG.debug("Using metadata source: '%s'", url2base[avail_url])
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.debug("Giving up on OpenStack md from %s after %s seconds",
Packit Service a04d08
                      md_urls, int(time.time() - start_time))
Packit Service a04d08
Packit Service a04d08
        self.metadata_address = url2base.get(avail_url)
Packit Service a04d08
        return bool(avail_url)
Packit Service a04d08
Packit Service a04d08
    def check_instance_id(self, sys_cfg):
Packit Service a04d08
        # quickly (local check only) if self.instance_id is still valid
Packit Service a04d08
        return sources.instance_id_matches_system_uuid(self.get_instance_id())
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def network_config(self):
Packit Service a04d08
        """Return a network config dict for rendering ENI or netplan files."""
Packit Service a04d08
        if self._network_config != sources.UNSET:
Packit Service a04d08
            return self._network_config
Packit Service a04d08
Packit Service a04d08
        # RELEASE_BLOCKER: SRU to Xenial and Artful SRU should not provide
Packit Service a04d08
        # network_config by default unless configured in /etc/cloud/cloud.cfg*.
Packit Service a04d08
        # Patch Xenial and Artful before release to default to False.
Packit Service a04d08
        if util.is_false(self.ds_cfg.get('apply_network_config', True)):
Packit Service a04d08
            self._network_config = None
Packit Service a04d08
            return self._network_config
Packit Service a04d08
        if self.network_json == sources.UNSET:
Packit Service a04d08
            # this would happen if get_data hadn't been called. leave as UNSET
Packit Service a04d08
            LOG.warning(
Packit Service a04d08
                'Unexpected call to network_config when network_json is None.')
Packit Service a04d08
            return None
Packit Service a04d08
Packit Service a04d08
        LOG.debug('network config provided via network_json')
Packit Service a04d08
        self._network_config = openstack.convert_net_json(
Packit Service a04d08
            self.network_json, known_macs=None)
Packit Service a04d08
        return self._network_config
Packit Service a04d08
Packit Service a04d08
    def _get_data(self):
Packit Service a04d08
        """Crawl metadata, parse and persist that data for this instance.
Packit Service a04d08
Packit Service a04d08
        @return: True when metadata discovered indicates OpenStack datasource.
Packit Service a04d08
            False when unable to contact metadata service or when metadata
Packit Service a04d08
            format is invalid or disabled.
Packit Service a04d08
        """
Packit Service a04d08
        oracle_considered = 'Oracle' in self.sys_cfg.get('datasource_list')
Packit Service a04d08
        if not detect_openstack(accept_oracle=not oracle_considered):
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        if self.perform_dhcp_setup:  # Setup networking in init-local stage.
Packit Service a04d08
            try:
Packit Service a04d08
                with EphemeralDHCPv4(self.fallback_interface):
Packit Service a04d08
                    results = util.log_time(
Packit Service a04d08
                        logfunc=LOG.debug, msg='Crawl of metadata service',
Packit Service a04d08
                        func=self._crawl_metadata)
Packit Service a04d08
            except (NoDHCPLeaseError, sources.InvalidMetaDataException) as e:
Packit Service a04d08
                util.logexc(LOG, str(e))
Packit Service a04d08
                return False
Packit Service a04d08
        else:
Packit Service a04d08
            try:
Packit Service a04d08
                results = self._crawl_metadata()
Packit Service a04d08
            except sources.InvalidMetaDataException as e:
Packit Service a04d08
                util.logexc(LOG, str(e))
Packit Service a04d08
                return False
Packit Service a04d08
Packit Service a04d08
        self.dsmode = self._determine_dsmode([results.get('dsmode')])
Packit Service a04d08
        if self.dsmode == sources.DSMODE_DISABLED:
Packit Service a04d08
            return False
Packit Service a04d08
        md = results.get('metadata', {})
Packit Service a04d08
        md = util.mergemanydict([md, DEFAULT_METADATA])
Packit Service a04d08
        self.metadata = md
Packit Service a04d08
        self.ec2_metadata = results.get('ec2-metadata')
Packit Service a04d08
        self.network_json = results.get('networkdata')
Packit Service a04d08
        self.userdata_raw = results.get('userdata')
Packit Service a04d08
        self.version = results['version']
Packit Service a04d08
        self.files.update(results.get('files', {}))
Packit Service a04d08
Packit Service a04d08
        vd = results.get('vendordata')
Packit Service a04d08
        self.vendordata_pure = vd
Packit Service a04d08
        try:
Packit Service a04d08
            self.vendordata_raw = sources.convert_vendordata(vd)
Packit Service a04d08
        except ValueError as e:
Packit Service a04d08
            LOG.warning("Invalid content in vendor-data: %s", e)
Packit Service a04d08
            self.vendordata_raw = None
Packit Service a04d08
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def _crawl_metadata(self):
Packit Service a04d08
        """Crawl metadata service when available.
Packit Service a04d08
Packit Service a04d08
        @returns: Dictionary with all metadata discovered for this datasource.
Packit Service a04d08
        @raise: InvalidMetaDataException on unreadable or broken
Packit Service a04d08
            metadata.
Packit Service a04d08
        """
Packit Service a04d08
        try:
Packit Service a04d08
            if not self.wait_for_metadata_service():
Packit Service a04d08
                raise sources.InvalidMetaDataException(
Packit Service a04d08
                    'No active metadata service found')
Packit Service a04d08
        except IOError as e:
Packit Service a04d08
            raise sources.InvalidMetaDataException(
Packit Service a04d08
                'IOError contacting metadata service: {error}'.format(
Packit Service a04d08
                    error=str(e)))
Packit Service a04d08
Packit Service a04d08
        url_params = self.get_url_params()
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            result = util.log_time(
Packit Service a04d08
                LOG.debug, 'Crawl of openstack metadata service',
Packit Service a04d08
                read_metadata_service, args=[self.metadata_address],
Packit Service a04d08
                kwargs={'ssl_details': self.ssl_details,
Packit Service a04d08
                        'retries': url_params.num_retries,
Packit Service a04d08
                        'timeout': url_params.timeout_seconds})
Packit Service a04d08
        except openstack.NonReadable as e:
Packit Service a04d08
            raise sources.InvalidMetaDataException(str(e))
Packit Service 11b429
        except (openstack.BrokenMetadata, IOError):
Packit Service a04d08
            msg = 'Broken metadata address {addr}'.format(
Packit Service a04d08
                addr=self.metadata_address)
Packit Service 11b429
            raise sources.InvalidMetaDataException(msg)
Packit Service a04d08
        return result
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DataSourceOpenStackLocal(DataSourceOpenStack):
Packit Service a04d08
    """Run in init-local using a dhcp discovery prior to metadata crawl.
Packit Service a04d08
Packit Service a04d08
    In init-local, no network is available. This subclass sets up minimal
Packit Service a04d08
    networking with dhclient on a viable nic so that it can talk to the
Packit Service a04d08
    metadata service. If the metadata service provides network configuration
Packit Service a04d08
    then render the network configuration for that instance based on metadata.
Packit Service a04d08
    """
Packit Service a04d08
Packit Service a04d08
    perform_dhcp_setup = True  # Get metadata network config if present
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def read_metadata_service(base_url, ssl_details=None,
Packit Service a04d08
                          timeout=5, retries=5):
Packit Service a04d08
    reader = openstack.MetadataReader(base_url, ssl_details=ssl_details,
Packit Service a04d08
                                      timeout=timeout, retries=retries)
Packit Service a04d08
    return reader.read_v2()
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def detect_openstack(accept_oracle=False):
Packit Service a04d08
    """Return True when a potential OpenStack platform is detected."""
Packit Service a04d08
    if not util.is_x86():
Packit Service a04d08
        return True  # Non-Intel cpus don't properly report dmi product names
Packit Service a04d08
    product_name = util.read_dmi_data('system-product-name')
Packit Service a04d08
    if product_name in VALID_DMI_PRODUCT_NAMES:
Packit Service a04d08
        return True
Packit Service a04d08
    elif util.read_dmi_data('chassis-asset-tag') in VALID_DMI_ASSET_TAGS:
Packit Service a04d08
        return True
Packit Service a04d08
    elif accept_oracle and oracle._is_platform_viable():
Packit Service a04d08
        return True
Packit Service a04d08
    elif util.get_proc_env(1).get('product_name') == DMI_PRODUCT_NOVA:
Packit Service a04d08
        return True
Packit Service a04d08
    return False
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Used to match classes to dependencies
Packit Service a04d08
datasources = [
Packit Service a04d08
    (DataSourceOpenStackLocal, (sources.DEP_FILESYSTEM,)),
Packit Service a04d08
    (DataSourceOpenStack, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
Packit Service a04d08
]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Return a list of data sources that match this set of dependencies
Packit Service a04d08
def get_datasource_list(depends):
Packit Service a04d08
    return sources.list_from_depends(depends, datasources)
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab