Blame cloudinit/sources/DataSourceAzure.py

Packit Service a04d08
# Copyright (C) 2013 Canonical Ltd.
Packit Service a04d08
#
Packit Service a04d08
# Author: Scott Moser <scott.moser@canonical.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 base64
Packit Service a04d08
import contextlib
Packit Service a04d08
import crypt
Packit Service a04d08
from functools import partial
Packit Service a04d08
import os
Packit Service a04d08
import os.path
Packit Service a04d08
import re
Packit Service a04d08
from time import time
Packit Service a04d08
from xml.dom import minidom
Packit Service a04d08
import xml.etree.ElementTree as ET
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit import net
Packit Service a04d08
from cloudinit.event import EventType
Packit Service a04d08
from cloudinit.net import device_driver
Packit Service a04d08
from cloudinit.net.dhcp import EphemeralDHCPv4
Packit Service a04d08
from cloudinit import sources
Packit Service a04d08
from cloudinit.sources.helpers import netlink
Packit Service a04d08
from cloudinit import subp
Packit Service a04d08
from cloudinit.url_helper import UrlError, readurl, retry_on_url_exc
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
from cloudinit.reporting import events
Packit Service a04d08
Packit Service a04d08
from cloudinit.sources.helpers.azure import (
Packit Service a04d08
    azure_ds_reporter,
Packit Service a04d08
    azure_ds_telemetry_reporter,
Packit Service a04d08
    get_metadata_from_fabric,
Packit Service a04d08
    get_boot_telemetry,
Packit Service a04d08
    get_system_info,
Packit Service a04d08
    report_diagnostic_event,
Packit Service a04d08
    EphemeralDHCPv4WithReporting,
Packit Service a04d08
    is_byte_swapped,
Packit Service a04d08
    dhcp_log_cb,
Packit Service a04d08
    push_log_to_kvp)
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
DS_NAME = 'Azure'
Packit Service a04d08
DEFAULT_METADATA = {"instance-id": "iid-AZURE-NODE"}
Packit Service a04d08
AGENT_START = ['service', 'walinuxagent', 'start']
Packit Service a04d08
AGENT_START_BUILTIN = "__builtin__"
Packit Service a04d08
BOUNCE_COMMAND_IFUP = [
Packit Service a04d08
    'sh', '-xc',
Packit Service a04d08
    "i=$interface; x=0; ifdown $i || x=$?; ifup $i || x=$?; exit $x"
Packit Service a04d08
]
Packit Service a04d08
BOUNCE_COMMAND_FREEBSD = [
Packit Service a04d08
    'sh', '-xc',
Packit Service a04d08
    ("i=$interface; x=0; ifconfig down $i || x=$?; "
Packit Service a04d08
     "ifconfig up $i || x=$?; exit $x")
Packit Service a04d08
]
Packit Service a04d08
Packit Service a04d08
# azure systems will always have a resource disk, and 66-azure-ephemeral.rules
Packit Service a04d08
# ensures that it gets linked to this path.
Packit Service a04d08
RESOURCE_DISK_PATH = '/dev/disk/cloud/azure_resource'
Packit Service a04d08
DEFAULT_PRIMARY_NIC = 'eth0'
Packit Service a04d08
LEASE_FILE = '/var/lib/dhcp/dhclient.eth0.leases'
Packit Service a04d08
DEFAULT_FS = 'ext4'
Packit Service a04d08
# DMI chassis-asset-tag is set static for all azure instances
Packit Service a04d08
AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77'
Packit Service a04d08
REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds"
Packit Service a04d08
REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready"
Packit Service a04d08
AGENT_SEED_DIR = '/var/lib/waagent'
Packit Service a04d08
Packit Service a04d08
# In the event where the IMDS primary server is not
Packit Service a04d08
# available, it takes 1s to fallback to the secondary one
Packit Service a04d08
IMDS_TIMEOUT_IN_SECONDS = 2
Packit Service a04d08
IMDS_URL = "http://169.254.169.254/metadata/"
Packit Service a04d08
Packit Service a04d08
PLATFORM_ENTROPY_SOURCE = "/sys/firmware/acpi/tables/OEM0"
Packit Service a04d08
Packit Service a04d08
# List of static scripts and network config artifacts created by
Packit Service a04d08
# stock ubuntu suported images.
Packit Service a04d08
UBUNTU_EXTENDED_NETWORK_SCRIPTS = [
Packit Service a04d08
    '/etc/netplan/90-hotplug-azure.yaml',
Packit Service a04d08
    '/usr/local/sbin/ephemeral_eth.sh',
Packit Service a04d08
    '/etc/udev/rules.d/10-net-device-added.rules',
Packit Service a04d08
    '/run/network/interfaces.ephemeral.d',
Packit Service a04d08
]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid):
Packit Service a04d08
    # extract the 'X' from dev.storvsc.X. if deviceid matches
Packit Service a04d08
    """
Packit Service a04d08
    dev.storvsc.1.%pnpinfo:
Packit Service a04d08
        classid=32412632-86cb-44a2-9b5c-50d1417354f5
Packit Service a04d08
        deviceid=00000000-0001-8899-0000-000000000000
Packit Service a04d08
    """
Packit Service a04d08
    for line in sysctl_out.splitlines():
Packit Service a04d08
        if re.search(r"pnpinfo", line):
Packit Service a04d08
            fields = line.split()
Packit Service a04d08
            if len(fields) >= 3:
Packit Service a04d08
                columns = fields[2].split('=')
Packit Service a04d08
                if (len(columns) >= 2 and
Packit Service a04d08
                        columns[0] == "deviceid" and
Packit Service a04d08
                        columns[1].startswith(deviceid)):
Packit Service a04d08
                    comps = fields[0].split('.')
Packit Service a04d08
                    return comps[2]
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def find_busdev_from_disk(camcontrol_out, disk_drv):
Packit Service a04d08
    # find the scbusX from 'camcontrol devlist -b' output
Packit Service a04d08
    # if disk_drv matches the specified disk driver, i.e. blkvsc1
Packit Service a04d08
    """
Packit Service a04d08
    scbus0 on ata0 bus 0
Packit Service a04d08
    scbus1 on ata1 bus 0
Packit Service a04d08
    scbus2 on blkvsc0 bus 0
Packit Service a04d08
    scbus3 on blkvsc1 bus 0
Packit Service a04d08
    scbus4 on storvsc2 bus 0
Packit Service a04d08
    scbus5 on storvsc3 bus 0
Packit Service a04d08
    scbus-1 on xpt0 bus 0
Packit Service a04d08
    """
Packit Service a04d08
    for line in camcontrol_out.splitlines():
Packit Service a04d08
        if re.search(disk_drv, line):
Packit Service a04d08
            items = line.split()
Packit Service a04d08
            return items[0]
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def find_dev_from_busdev(camcontrol_out, busdev):
Packit Service a04d08
    # find the daX from 'camcontrol devlist' output
Packit Service a04d08
    # if busdev matches the specified value, i.e. 'scbus2'
Packit Service a04d08
    """
Packit Service a04d08
    <Msft Virtual CD/ROM 1.0>          at scbus1 target 0 lun 0 (cd0,pass0)
Packit Service a04d08
    <Msft Virtual Disk 1.0>            at scbus2 target 0 lun 0 (da0,pass1)
Packit Service a04d08
    <Msft Virtual Disk 1.0>            at scbus3 target 1 lun 0 (da1,pass2)
Packit Service a04d08
    """
Packit Service a04d08
    for line in camcontrol_out.splitlines():
Packit Service a04d08
        if re.search(busdev, line):
Packit Service a04d08
            items = line.split('(')
Packit Service a04d08
            if len(items) == 2:
Packit Service a04d08
                dev_pass = items[1].split(',')
Packit Service a04d08
                return dev_pass[0]
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def execute_or_debug(cmd, fail_ret=None):
Packit Service a04d08
    try:
Packit Service a04d08
        return subp.subp(cmd)[0]
Packit Service a04d08
    except subp.ProcessExecutionError:
Packit Service a04d08
        LOG.debug("Failed to execute: %s", ' '.join(cmd))
Packit Service a04d08
        return fail_ret
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_dev_storvsc_sysctl():
Packit Service a04d08
    return execute_or_debug(["sysctl", "dev.storvsc"], fail_ret="")
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_camcontrol_dev_bus():
Packit Service a04d08
    return execute_or_debug(['camcontrol', 'devlist', '-b'])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_camcontrol_dev():
Packit Service a04d08
    return execute_or_debug(['camcontrol', 'devlist'])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_resource_disk_on_freebsd(port_id):
Packit Service a04d08
    g0 = "00000000"
Packit Service a04d08
    if port_id > 1:
Packit Service a04d08
        g0 = "00000001"
Packit Service a04d08
        port_id = port_id - 2
Packit Service a04d08
    g1 = "000" + str(port_id)
Packit Service a04d08
    g0g1 = "{0}-{1}".format(g0, g1)
Packit Service a04d08
Packit Service a04d08
    # search 'X' from
Packit Service a04d08
    #  'dev.storvsc.X.%pnpinfo:
Packit Service a04d08
    #      classid=32412632-86cb-44a2-9b5c-50d1417354f5
Packit Service a04d08
    #      deviceid=00000000-0001-8899-0000-000000000000'
Packit Service a04d08
    sysctl_out = get_dev_storvsc_sysctl()
Packit Service a04d08
Packit Service a04d08
    storvscid = find_storvscid_from_sysctl_pnpinfo(sysctl_out, g0g1)
Packit Service a04d08
    if not storvscid:
Packit Service a04d08
        LOG.debug("Fail to find storvsc id from sysctl")
Packit Service a04d08
        return None
Packit Service a04d08
Packit Service a04d08
    camcontrol_b_out = get_camcontrol_dev_bus()
Packit Service a04d08
    camcontrol_out = get_camcontrol_dev()
Packit Service a04d08
    # try to find /dev/XX from 'blkvsc' device
Packit Service a04d08
    blkvsc = "blkvsc{0}".format(storvscid)
Packit Service a04d08
    scbusx = find_busdev_from_disk(camcontrol_b_out, blkvsc)
Packit Service a04d08
    if scbusx:
Packit Service a04d08
        devname = find_dev_from_busdev(camcontrol_out, scbusx)
Packit Service a04d08
        if devname is None:
Packit Service a04d08
            LOG.debug("Fail to find /dev/daX")
Packit Service a04d08
            return None
Packit Service a04d08
        return devname
Packit Service a04d08
    # try to find /dev/XX from 'storvsc' device
Packit Service a04d08
    storvsc = "storvsc{0}".format(storvscid)
Packit Service a04d08
    scbusx = find_busdev_from_disk(camcontrol_b_out, storvsc)
Packit Service a04d08
    if scbusx:
Packit Service a04d08
        devname = find_dev_from_busdev(camcontrol_out, scbusx)
Packit Service a04d08
        if devname is None:
Packit Service a04d08
            LOG.debug("Fail to find /dev/daX")
Packit Service a04d08
            return None
Packit Service a04d08
        return devname
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# update the FreeBSD specific information
Packit Service a04d08
if util.is_FreeBSD():
Packit Service a04d08
    DEFAULT_PRIMARY_NIC = 'hn0'
Packit Service a04d08
    LEASE_FILE = '/var/db/dhclient.leases.hn0'
Packit Service a04d08
    DEFAULT_FS = 'freebsd-ufs'
Packit Service a04d08
    res_disk = get_resource_disk_on_freebsd(1)
Packit Service a04d08
    if res_disk is not None:
Packit Service a04d08
        LOG.debug("resource disk is not None")
Packit Service a04d08
        RESOURCE_DISK_PATH = "/dev/" + res_disk
Packit Service a04d08
    else:
Packit Service a04d08
        LOG.debug("resource disk is None")
Packit Service a04d08
    # TODO Find where platform entropy data is surfaced
Packit Service a04d08
    PLATFORM_ENTROPY_SOURCE = None
Packit Service a04d08
Packit Service a04d08
BUILTIN_DS_CONFIG = {
Packit Service a04d08
    'agent_command': AGENT_START_BUILTIN,
Packit Service a04d08
    'data_dir': AGENT_SEED_DIR,
Packit Service a04d08
    'set_hostname': True,
Packit Service a04d08
    'hostname_bounce': {
Packit Service a04d08
        'interface': DEFAULT_PRIMARY_NIC,
Packit Service a04d08
        'policy': True,
Packit Service a04d08
        'command': 'builtin',
Packit Service a04d08
        'hostname_command': 'hostname',
Packit Service a04d08
    },
Packit Service a04d08
    'disk_aliases': {'ephemeral0': RESOURCE_DISK_PATH},
Packit Service a04d08
    'dhclient_lease_file': LEASE_FILE,
Packit Service a04d08
    'apply_network_config': True,  # Use IMDS published network configuration
Packit Service a04d08
}
Packit Service a04d08
# RELEASE_BLOCKER: Xenial and earlier apply_network_config default is False
Packit Service a04d08
Packit Service a04d08
BUILTIN_CLOUD_CONFIG = {
Packit Service a04d08
    'disk_setup': {
Packit Service a04d08
        'ephemeral0': {'table_type': 'gpt',
Packit Service a04d08
                       'layout': [100],
Packit Service a04d08
                       'overwrite': True},
Packit Service a04d08
    },
Packit Service a04d08
    'fs_setup': [{'filesystem': DEFAULT_FS,
Packit Service a04d08
                  'device': 'ephemeral0.1'}],
Packit Service a04d08
}
Packit Service a04d08
Packit Service a04d08
DS_CFG_PATH = ['datasource', DS_NAME]
Packit Service a04d08
DS_CFG_KEY_PRESERVE_NTFS = 'never_destroy_ntfs'
Packit Service a04d08
DEF_EPHEMERAL_LABEL = 'Temporary Storage'
Packit Service a04d08
Packit Service a04d08
# The redacted password fails to meet password complexity requirements
Packit Service a04d08
# so we can safely use this to mask/redact the password in the ovf-env.xml
Packit Service a04d08
DEF_PASSWD_REDACTION = 'REDACTED'
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_hostname(hostname_command='hostname'):
Packit Service a04d08
    if not isinstance(hostname_command, (list, tuple)):
Packit Service a04d08
        hostname_command = (hostname_command,)
Packit Service a04d08
    return subp.subp(hostname_command, capture=True)[0].strip()
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def set_hostname(hostname, hostname_command='hostname'):
Packit Service 0497d1
    util.subp(['hostnamectl', 'set-hostname', str(hostname)])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
@contextlib.contextmanager
Packit Service a04d08
def temporary_hostname(temp_hostname, cfg, hostname_command='hostname'):
Packit Service a04d08
    """
Packit Service a04d08
    Set a temporary hostname, restoring the previous hostname on exit.
Packit Service a04d08
Packit Service a04d08
    Will have the value of the previous hostname when used as a context
Packit Service a04d08
    manager, or None if the hostname was not changed.
Packit Service a04d08
    """
Packit Service a04d08
    policy = cfg['hostname_bounce']['policy']
Packit Service a04d08
    previous_hostname = get_hostname(hostname_command)
Packit Service a04d08
    if (not util.is_true(cfg.get('set_hostname')) or
Packit Service a04d08
       util.is_false(policy) or
Packit Service a04d08
       (previous_hostname == temp_hostname and policy != 'force')):
Packit Service a04d08
        yield None
Packit Service a04d08
        return
Packit Service a04d08
    try:
Packit Service a04d08
        set_hostname(temp_hostname, hostname_command)
Packit Service a04d08
    except Exception as e:
Packit Service a04d08
        msg = 'Failed setting temporary hostname: %s' % e
Packit Service a04d08
        report_diagnostic_event(msg)
Packit Service a04d08
        LOG.warning(msg)
Packit Service a04d08
        yield None
Packit Service a04d08
        return
Packit Service a04d08
    try:
Packit Service a04d08
        yield previous_hostname
Packit Service a04d08
    finally:
Packit Service a04d08
        set_hostname(previous_hostname, hostname_command)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DataSourceAzure(sources.DataSource):
Packit Service a04d08
Packit Service a04d08
    dsname = 'Azure'
Packit Service a04d08
    _negotiated = False
Packit Service a04d08
    _metadata_imds = sources.UNSET
Packit Service a04d08
Packit Service a04d08
    def __init__(self, sys_cfg, distro, paths):
Packit Service a04d08
        sources.DataSource.__init__(self, sys_cfg, distro, paths)
Packit Service a04d08
        self.seed_dir = os.path.join(paths.seed_dir, 'azure')
Packit Service a04d08
        self.cfg = {}
Packit Service a04d08
        self.seed = None
Packit Service a04d08
        self.ds_cfg = util.mergemanydict([
Packit Service a04d08
            util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}),
Packit Service a04d08
            BUILTIN_DS_CONFIG])
Packit Service a04d08
        self.dhclient_lease_file = self.ds_cfg.get('dhclient_lease_file')
Packit Service a04d08
        self._network_config = None
Packit Service a04d08
        # Regenerate network config new_instance boot and every boot
Packit Service a04d08
        self.update_events['network'].add(EventType.BOOT)
Packit Service a04d08
        self._ephemeral_dhcp_ctx = None
Packit Service a04d08
Packit Service a04d08
    def __str__(self):
Packit Service a04d08
        root = sources.DataSource.__str__(self)
Packit Service a04d08
        return "%s [seed=%s]" % (root, self.seed)
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def bounce_network_with_azure_hostname(self):
Packit Service a04d08
        # When using cloud-init to provision, we have to set the hostname from
Packit Service a04d08
        # the metadata and "bounce" the network to force DDNS to update via
Packit Service a04d08
        # dhclient
Packit Service a04d08
        azure_hostname = self.metadata.get('local-hostname')
Packit Service a04d08
        LOG.debug("Hostname in metadata is %s", azure_hostname)
Packit Service a04d08
        hostname_command = self.ds_cfg['hostname_bounce']['hostname_command']
Packit Service a04d08
Packit Service a04d08
        with temporary_hostname(azure_hostname, self.ds_cfg,
Packit Service a04d08
                                hostname_command=hostname_command) \
Packit Service a04d08
                as previous_hn:
Packit Service a04d08
            if (previous_hn is not None and
Packit Service a04d08
                    util.is_true(self.ds_cfg.get('set_hostname'))):
Packit Service a04d08
                cfg = self.ds_cfg['hostname_bounce']
Packit Service a04d08
Packit Service a04d08
                # "Bouncing" the network
Packit Service a04d08
                try:
Packit Service a04d08
                    return perform_hostname_bounce(hostname=azure_hostname,
Packit Service a04d08
                                                   cfg=cfg,
Packit Service a04d08
                                                   prev_hostname=previous_hn)
Packit Service a04d08
                except Exception as e:
Packit Service a04d08
                    LOG.warning("Failed publishing hostname: %s", e)
Packit Service a04d08
                    util.logexc(LOG, "handling set_hostname failed")
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def get_metadata_from_agent(self):
Packit Service a04d08
        temp_hostname = self.metadata.get('local-hostname')
Packit Service a04d08
        agent_cmd = self.ds_cfg['agent_command']
Packit Service a04d08
        LOG.debug("Getting metadata via agent.  hostname=%s cmd=%s",
Packit Service a04d08
                  temp_hostname, agent_cmd)
Packit Service a04d08
Packit Service a04d08
        self.bounce_network_with_azure_hostname()
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            invoke_agent(agent_cmd)
Packit Service a04d08
        except subp.ProcessExecutionError:
Packit Service a04d08
            # claim the datasource even if the command failed
Packit Service a04d08
            util.logexc(LOG, "agent command '%s' failed.",
Packit Service a04d08
                        self.ds_cfg['agent_command'])
Packit Service a04d08
Packit Service a04d08
        ddir = self.ds_cfg['data_dir']
Packit Service a04d08
Packit Service a04d08
        fp_files = []
Packit Service a04d08
        key_value = None
Packit Service a04d08
        for pk in self.cfg.get('_pubkeys', []):
Packit Service a04d08
            if pk.get('value', None):
Packit Service a04d08
                key_value = pk['value']
Packit Service a04d08
                LOG.debug("SSH authentication: using value from fabric")
Packit Service a04d08
            else:
Packit Service a04d08
                bname = str(pk['fingerprint'] + ".crt")
Packit Service a04d08
                fp_files += [os.path.join(ddir, bname)]
Packit Service a04d08
                LOG.debug("SSH authentication: "
Packit Service a04d08
                          "using fingerprint from fabric")
Packit Service a04d08
Packit Service a04d08
        with events.ReportEventStack(
Packit Service a04d08
                name="waiting-for-ssh-public-key",
Packit Service a04d08
                description="wait for agents to retrieve SSH keys",
Packit Service a04d08
                parent=azure_ds_reporter):
Packit Service a04d08
            # wait very long for public SSH keys to arrive
Packit Service a04d08
            # https://bugs.launchpad.net/cloud-init/+bug/1717611
Packit Service a04d08
            missing = util.log_time(logfunc=LOG.debug,
Packit Service a04d08
                                    msg="waiting for SSH public key files",
Packit Service a04d08
                                    func=util.wait_for_files,
Packit Service a04d08
                                    args=(fp_files, 900))
Packit Service a04d08
            if len(missing):
Packit Service a04d08
                LOG.warning("Did not find files, but going on: %s", missing)
Packit Service a04d08
Packit Service a04d08
        metadata = {}
Packit Service a04d08
        metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files)
Packit Service a04d08
        return metadata
Packit Service a04d08
Packit Service a04d08
    def _get_subplatform(self):
Packit Service a04d08
        """Return the subplatform metadata source details."""
Packit Service a04d08
        if self.seed.startswith('/dev'):
Packit Service a04d08
            subplatform_type = 'config-disk'
Packit Service a04d08
        else:
Packit Service a04d08
            subplatform_type = 'seed-dir'
Packit Service a04d08
        return '%s (%s)' % (subplatform_type, self.seed)
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def crawl_metadata(self):
Packit Service a04d08
        """Walk all instance metadata sources returning a dict on success.
Packit Service a04d08
Packit Service a04d08
        @return: A dictionary of any metadata content for this instance.
Packit Service a04d08
        @raise: InvalidMetaDataException when the expected metadata service is
Packit Service a04d08
            unavailable, broken or disabled.
Packit Service a04d08
        """
Packit Service a04d08
        crawled_data = {}
Packit Service a04d08
        # azure removes/ejects the cdrom containing the ovf-env.xml
Packit Service a04d08
        # file on reboot.  So, in order to successfully reboot we
Packit Service a04d08
        # need to look in the datadir and consider that valid
Packit Service a04d08
        ddir = self.ds_cfg['data_dir']
Packit Service a04d08
Packit Service a04d08
        candidates = [self.seed_dir]
Packit Service a04d08
        if os.path.isfile(REPROVISION_MARKER_FILE):
Packit Service a04d08
            candidates.insert(0, "IMDS")
Packit Service a04d08
        candidates.extend(list_possible_azure_ds_devs())
Packit Service a04d08
        if ddir:
Packit Service a04d08
            candidates.append(ddir)
Packit Service a04d08
Packit Service a04d08
        found = None
Packit Service a04d08
        reprovision = False
Packit Service a04d08
        for cdev in candidates:
Packit Service a04d08
            try:
Packit Service a04d08
                if cdev == "IMDS":
Packit Service a04d08
                    ret = None
Packit Service a04d08
                    reprovision = True
Packit Service a04d08
                elif cdev.startswith("/dev/"):
Packit Service a04d08
                    if util.is_FreeBSD():
Packit Service a04d08
                        ret = util.mount_cb(cdev, load_azure_ds_dir,
Packit Service a04d08
                                            mtype="udf")
Packit Service a04d08
                    else:
Packit Service a04d08
                        ret = util.mount_cb(cdev, load_azure_ds_dir)
Packit Service a04d08
                else:
Packit Service a04d08
                    ret = load_azure_ds_dir(cdev)
Packit Service a04d08
Packit Service a04d08
            except NonAzureDataSource:
Packit Service a04d08
                report_diagnostic_event(
Packit Service a04d08
                    "Did not find Azure data source in %s" % cdev)
Packit Service a04d08
                continue
Packit Service a04d08
            except BrokenAzureDataSource as exc:
Packit Service a04d08
                msg = 'BrokenAzureDataSource: %s' % exc
Packit Service a04d08
                report_diagnostic_event(msg)
Packit Service a04d08
                raise sources.InvalidMetaDataException(msg)
Packit Service a04d08
            except util.MountFailedError:
Packit Service a04d08
                msg = '%s was not mountable' % cdev
Packit Service a04d08
                report_diagnostic_event(msg)
Packit Service a04d08
                LOG.warning(msg)
Packit Service a04d08
                continue
Packit Service a04d08
Packit Service a04d08
            perform_reprovision = reprovision or self._should_reprovision(ret)
Packit Service a04d08
            if perform_reprovision:
Packit Service a04d08
                if util.is_FreeBSD():
Packit Service a04d08
                    msg = "Free BSD is not supported for PPS VMs"
Packit Service a04d08
                    LOG.error(msg)
Packit Service a04d08
                    report_diagnostic_event(msg)
Packit Service a04d08
                    raise sources.InvalidMetaDataException(msg)
Packit Service a04d08
                ret = self._reprovision()
Packit Service a04d08
            imds_md = get_metadata_from_imds(
Packit Service a04d08
                self.fallback_interface, retries=10)
Packit Service a04d08
            (md, userdata_raw, cfg, files) = ret
Packit Service a04d08
            self.seed = cdev
Packit Service a04d08
            crawled_data.update({
Packit Service a04d08
                'cfg': cfg,
Packit Service a04d08
                'files': files,
Packit Service a04d08
                'metadata': util.mergemanydict(
Packit Service a04d08
                    [md, {'imds': imds_md}]),
Packit Service a04d08
                'userdata_raw': userdata_raw})
Packit Service a04d08
            found = cdev
Packit Service a04d08
Packit Service a04d08
            LOG.debug("found datasource in %s", cdev)
Packit Service a04d08
            break
Packit Service a04d08
Packit Service a04d08
        if not found:
Packit Service a04d08
            msg = 'No Azure metadata found'
Packit Service a04d08
            report_diagnostic_event(msg)
Packit Service a04d08
            raise sources.InvalidMetaDataException(msg)
Packit Service a04d08
Packit Service a04d08
        if found == ddir:
Packit Service a04d08
            LOG.debug("using files cached in %s", ddir)
Packit Service a04d08
Packit Service a04d08
        seed = _get_random_seed()
Packit Service a04d08
        if seed:
Packit Service a04d08
            crawled_data['metadata']['random_seed'] = seed
Packit Service a04d08
        crawled_data['metadata']['instance-id'] = self._iid()
Packit Service a04d08
Packit Service a04d08
        if perform_reprovision:
Packit Service a04d08
            LOG.info("Reporting ready to Azure after getting ReprovisionData")
Packit Service a04d08
            use_cached_ephemeral = (net.is_up(self.fallback_interface) and
Packit Service a04d08
                                    getattr(self, '_ephemeral_dhcp_ctx', None))
Packit Service a04d08
            if use_cached_ephemeral:
Packit Service a04d08
                self._report_ready(lease=self._ephemeral_dhcp_ctx.lease)
Packit Service a04d08
                self._ephemeral_dhcp_ctx.clean_network()  # Teardown ephemeral
Packit Service a04d08
            else:
Packit Service a04d08
                try:
Packit Service a04d08
                    with EphemeralDHCPv4WithReporting(
Packit Service a04d08
                            azure_ds_reporter) as lease:
Packit Service a04d08
                        self._report_ready(lease=lease)
Packit Service a04d08
                except Exception as e:
Packit Service a04d08
                    report_diagnostic_event(
Packit Service a04d08
                        "exception while reporting ready: %s" % e)
Packit Service a04d08
                    raise
Packit Service a04d08
        return crawled_data
Packit Service a04d08
Packit Service a04d08
    def _is_platform_viable(self):
Packit Service a04d08
        """Check platform environment to report if this datasource may run."""
Packit Service a04d08
        return _is_platform_viable(self.seed_dir)
Packit Service a04d08
Packit Service a04d08
    def clear_cached_attrs(self, attr_defaults=()):
Packit Service a04d08
        """Reset any cached class attributes to defaults."""
Packit Service a04d08
        super(DataSourceAzure, self).clear_cached_attrs(attr_defaults)
Packit Service a04d08
        self._metadata_imds = sources.UNSET
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _get_data(self):
Packit Service a04d08
        """Crawl and process datasource metadata caching metadata as attrs.
Packit Service a04d08
Packit Service a04d08
        @return: True on success, False on error, invalid or disabled
Packit Service a04d08
            datasource.
Packit Service a04d08
        """
Packit Service a04d08
        if not self._is_platform_viable():
Packit Service a04d08
            return False
Packit Service a04d08
        try:
Packit Service a04d08
            get_boot_telemetry()
Packit Service a04d08
        except Exception as e:
Packit Service a04d08
            LOG.warning("Failed to get boot telemetry: %s", e)
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            get_system_info()
Packit Service a04d08
        except Exception as e:
Packit Service a04d08
            LOG.warning("Failed to get system information: %s", e)
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            crawled_data = util.log_time(
Packit Service a04d08
                logfunc=LOG.debug, msg='Crawl of metadata service',
Packit Service a04d08
                func=self.crawl_metadata
Packit Service a04d08
            )
Packit Service a04d08
        except sources.InvalidMetaDataException as e:
Packit Service a04d08
            LOG.warning('Could not crawl Azure metadata: %s', e)
Packit Service a04d08
            return False
Packit Service a04d08
        if (self.distro and self.distro.name == 'ubuntu' and
Packit Service a04d08
                self.ds_cfg.get('apply_network_config')):
Packit Service a04d08
            maybe_remove_ubuntu_network_config_scripts()
Packit Service a04d08
Packit Service a04d08
        # Process crawled data and augment with various config defaults
Packit Service a04d08
        self.cfg = util.mergemanydict(
Packit Service a04d08
            [crawled_data['cfg'], BUILTIN_CLOUD_CONFIG])
Packit Service a04d08
        self._metadata_imds = crawled_data['metadata']['imds']
Packit Service a04d08
        self.metadata = util.mergemanydict(
Packit Service a04d08
            [crawled_data['metadata'], DEFAULT_METADATA])
Packit Service a04d08
        self.userdata_raw = crawled_data['userdata_raw']
Packit Service a04d08
Packit Service a04d08
        user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
Packit Service a04d08
        self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
Packit Service a04d08
Packit Service a04d08
        # walinux agent writes files world readable, but expects
Packit Service a04d08
        # the directory to be protected.
Packit Service a04d08
        write_files(
Packit Service a04d08
            self.ds_cfg['data_dir'], crawled_data['files'], dirmode=0o700)
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def device_name_to_device(self, name):
Packit Service a04d08
        return self.ds_cfg['disk_aliases'].get(name)
Packit Service a04d08
Packit Service a04d08
    def get_config_obj(self):
Packit Service a04d08
        return self.cfg
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
    def _iid(self, previous=None):
Packit Service a04d08
        prev_iid_path = os.path.join(
Packit Service a04d08
            self.paths.get_cpath('data'), 'instance-id')
Packit Service a04d08
        iid = util.read_dmi_data('system-uuid')
Packit Service a04d08
        if os.path.exists(prev_iid_path):
Packit Service a04d08
            previous = util.load_file(prev_iid_path).strip()
Packit Service a04d08
            if is_byte_swapped(previous, iid):
Packit Service a04d08
                return previous
Packit Service a04d08
        return iid
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def setup(self, is_new_instance):
Packit Service a04d08
        if self._negotiated is False:
Packit Service a04d08
            LOG.debug("negotiating for %s (new_instance=%s)",
Packit Service a04d08
                      self.get_instance_id(), is_new_instance)
Packit Service a04d08
            fabric_data = self._negotiate()
Packit Service a04d08
            LOG.debug("negotiating returned %s", fabric_data)
Packit Service a04d08
            if fabric_data:
Packit Service a04d08
                self.metadata.update(fabric_data)
Packit Service a04d08
            self._negotiated = True
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.debug("negotiating already done for %s",
Packit Service a04d08
                      self.get_instance_id())
Packit Service a04d08
Packit Service a04d08
    def _poll_imds(self):
Packit Service a04d08
        """Poll IMDS for the new provisioning data until we get a valid
Packit Service a04d08
        response. Then return the returned JSON object."""
Packit Service a04d08
        url = IMDS_URL + "reprovisiondata?api-version=2017-04-02"
Packit Service a04d08
        headers = {"Metadata": "true"}
Packit Service a04d08
        nl_sock = None
Packit Service a04d08
        report_ready = bool(not os.path.isfile(REPORTED_READY_MARKER_FILE))
Packit Service a04d08
        self.imds_logging_threshold = 1
Packit Service a04d08
        self.imds_poll_counter = 1
Packit Service a04d08
        dhcp_attempts = 0
Packit Service a04d08
        vnet_switched = False
Packit Service a04d08
        return_val = None
Packit Service a04d08
Packit Service a04d08
        def exc_cb(msg, exception):
Packit Service a04d08
            if isinstance(exception, UrlError):
Packit Service a04d08
                if exception.code in (404, 410):
Packit Service a04d08
                    if self.imds_poll_counter == self.imds_logging_threshold:
Packit Service a04d08
                        # Reducing the logging frequency as we are polling IMDS
Packit Service a04d08
                        self.imds_logging_threshold *= 2
Packit Service a04d08
                        LOG.debug("Call to IMDS with arguments %s failed "
Packit Service a04d08
                                  "with status code %s after %s retries",
Packit Service a04d08
                                  msg, exception.code, self.imds_poll_counter)
Packit Service a04d08
                        LOG.debug("Backing off logging threshold for the same "
Packit Service a04d08
                                  "exception to %d",
Packit Service a04d08
                                  self.imds_logging_threshold)
Packit Service a04d08
                        report_diagnostic_event("poll IMDS with %s failed. "
Packit Service a04d08
                                                "Exception: %s and code: %s" %
Packit Service a04d08
                                                (msg, exception.cause,
Packit Service a04d08
                                                 exception.code))
Packit Service a04d08
                    self.imds_poll_counter += 1
Packit Service a04d08
                    return True
Packit Service a04d08
                else:
Packit Service a04d08
                    # If we get an exception while trying to call IMDS, we call
Packit Service a04d08
                    # DHCP and setup the ephemeral network to acquire a new IP.
Packit Service a04d08
                    report_diagnostic_event("poll IMDS with %s failed. "
Packit Service a04d08
                                            "Exception: %s and code: %s" %
Packit Service a04d08
                                            (msg, exception.cause,
Packit Service a04d08
                                             exception.code))
Packit Service a04d08
                    return False
Packit Service a04d08
Packit Service a04d08
                LOG.debug("poll IMDS failed with an unexpected exception: %s",
Packit Service a04d08
                          exception)
Packit Service a04d08
                return False
Packit Service a04d08
Packit Service a04d08
        LOG.debug("Wait for vnetswitch to happen")
Packit Service a04d08
        while True:
Packit Service a04d08
            try:
Packit Service a04d08
                # Save our EphemeralDHCPv4 context to avoid repeated dhcp
Packit Service a04d08
                with events.ReportEventStack(
Packit Service a04d08
                        name="obtain-dhcp-lease",
Packit Service a04d08
                        description="obtain dhcp lease",
Packit Service a04d08
                        parent=azure_ds_reporter):
Packit Service a04d08
                    self._ephemeral_dhcp_ctx = EphemeralDHCPv4(
Packit Service a04d08
                        dhcp_log_func=dhcp_log_cb)
Packit Service a04d08
                    lease = self._ephemeral_dhcp_ctx.obtain_lease()
Packit Service a04d08
Packit Service a04d08
                if vnet_switched:
Packit Service a04d08
                    dhcp_attempts += 1
Packit Service a04d08
                if report_ready:
Packit Service a04d08
                    try:
Packit Service a04d08
                        nl_sock = netlink.create_bound_netlink_socket()
Packit Service a04d08
                    except netlink.NetlinkCreateSocketError as e:
Packit Service a04d08
                        report_diagnostic_event(e)
Packit Service a04d08
                        LOG.warning(e)
Packit Service a04d08
                        self._ephemeral_dhcp_ctx.clean_network()
Packit Service a04d08
                        break
Packit Service a04d08
Packit Service a04d08
                    path = REPORTED_READY_MARKER_FILE
Packit Service a04d08
                    LOG.info(
Packit Service a04d08
                        "Creating a marker file to report ready: %s", path)
Packit Service a04d08
                    util.write_file(path, "{pid}: {time}\n".format(
Packit Service a04d08
                        pid=os.getpid(), time=time()))
Packit Service a04d08
                    self._report_ready(lease=lease)
Packit Service a04d08
                    report_ready = False
Packit Service a04d08
Packit Service a04d08
                    with events.ReportEventStack(
Packit Service a04d08
                            name="wait-for-media-disconnect-connect",
Packit Service a04d08
                            description="wait for vnet switch",
Packit Service a04d08
                            parent=azure_ds_reporter):
Packit Service a04d08
                        try:
Packit Service a04d08
                            netlink.wait_for_media_disconnect_connect(
Packit Service a04d08
                                nl_sock, lease['interface'])
Packit Service a04d08
                        except AssertionError as error:
Packit Service a04d08
                            report_diagnostic_event(error)
Packit Service a04d08
                            LOG.error(error)
Packit Service a04d08
                            break
Packit Service a04d08
Packit Service a04d08
                    vnet_switched = True
Packit Service a04d08
                    self._ephemeral_dhcp_ctx.clean_network()
Packit Service a04d08
                else:
Packit Service a04d08
                    with events.ReportEventStack(
Packit Service a04d08
                            name="get-reprovision-data-from-imds",
Packit Service a04d08
                            description="get reprovision data from imds",
Packit Service a04d08
                            parent=azure_ds_reporter):
Packit Service a04d08
                        return_val = readurl(url,
Packit Service a04d08
                                             timeout=IMDS_TIMEOUT_IN_SECONDS,
Packit Service a04d08
                                             headers=headers,
Packit Service a04d08
                                             exception_cb=exc_cb,
Packit Service a04d08
                                             infinite=True,
Packit Service a04d08
                                             log_req_resp=False).contents
Packit Service a04d08
                    break
Packit Service a04d08
            except UrlError:
Packit Service a04d08
                # Teardown our EphemeralDHCPv4 context on failure as we retry
Packit Service a04d08
                self._ephemeral_dhcp_ctx.clean_network()
Packit Service a04d08
            finally:
Packit Service a04d08
                if nl_sock:
Packit Service a04d08
                    nl_sock.close()
Packit Service a04d08
Packit Service a04d08
        if vnet_switched:
Packit Service a04d08
            report_diagnostic_event("attempted dhcp %d times after reuse" %
Packit Service a04d08
                                    dhcp_attempts)
Packit Service a04d08
            report_diagnostic_event("polled imds %d times after reuse" %
Packit Service a04d08
                                    self.imds_poll_counter)
Packit Service a04d08
Packit Service a04d08
        return return_val
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _report_ready(self, lease):
Packit Service a04d08
        """Tells the fabric provisioning has completed """
Packit Service a04d08
        try:
Packit Service a04d08
            get_metadata_from_fabric(None, lease['unknown-245'])
Packit Service a04d08
        except Exception:
Packit Service a04d08
            LOG.warning(
Packit Service a04d08
                "Error communicating with Azure fabric; You may experience."
Packit Service a04d08
                "connectivity issues.", exc_info=True)
Packit Service a04d08
Packit Service a04d08
    def _should_reprovision(self, ret):
Packit Service a04d08
        """Whether or not we should poll IMDS for reprovisioning data.
Packit Service a04d08
        Also sets a marker file to poll IMDS.
Packit Service a04d08
Packit Service a04d08
        The marker file is used for the following scenario: the VM boots into
Packit Service a04d08
        this polling loop, which we expect to be proceeding infinitely until
Packit Service a04d08
        the VM is picked. If for whatever reason the platform moves us to a
Packit Service a04d08
        new host (for instance a hardware issue), we need to keep polling.
Packit Service a04d08
        However, since the VM reports ready to the Fabric, we will not attach
Packit Service a04d08
        the ISO, thus cloud-init needs to have a way of knowing that it should
Packit Service a04d08
        jump back into the polling loop in order to retrieve the ovf_env."""
Packit Service a04d08
        if not ret:
Packit Service a04d08
            return False
Packit Service a04d08
        (_md, _userdata_raw, cfg, _files) = ret
Packit Service a04d08
        path = REPROVISION_MARKER_FILE
Packit Service a04d08
        if (cfg.get('PreprovisionedVm') is True or
Packit Service a04d08
                os.path.isfile(path)):
Packit Service a04d08
            if not os.path.isfile(path):
Packit Service a04d08
                LOG.info("Creating a marker file to poll imds: %s",
Packit Service a04d08
                         path)
Packit Service a04d08
                util.write_file(path, "{pid}: {time}\n".format(
Packit Service a04d08
                    pid=os.getpid(), time=time()))
Packit Service a04d08
            return True
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
    def _reprovision(self):
Packit Service a04d08
        """Initiate the reprovisioning workflow."""
Packit Service a04d08
        contents = self._poll_imds()
Packit Service a04d08
        with events.ReportEventStack(
Packit Service a04d08
                name="reprovisioning-read-azure-ovf",
Packit Service a04d08
                description="read azure ovf during reprovisioning",
Packit Service a04d08
                parent=azure_ds_reporter):
Packit Service a04d08
            md, ud, cfg = read_azure_ovf(contents)
Packit Service a04d08
            return (md, ud, cfg, {'ovf-env.xml': contents})
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _negotiate(self):
Packit Service a04d08
        """Negotiate with fabric and return data from it.
Packit Service a04d08
Packit Service a04d08
           On success, returns a dictionary including 'public_keys'.
Packit Service a04d08
           On failure, returns False.
Packit Service a04d08
        """
Packit Service a04d08
Packit Service a04d08
        if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN:
Packit Service a04d08
            self.bounce_network_with_azure_hostname()
Packit Service a04d08
Packit Service a04d08
            pubkey_info = self.cfg.get('_pubkeys', None)
Packit Service a04d08
            metadata_func = partial(get_metadata_from_fabric,
Packit Service a04d08
                                    fallback_lease_file=self.
Packit Service a04d08
                                    dhclient_lease_file,
Packit Service a04d08
                                    pubkey_info=pubkey_info)
Packit Service a04d08
        else:
Packit Service a04d08
            metadata_func = self.get_metadata_from_agent
Packit Service a04d08
Packit Service a04d08
        LOG.debug("negotiating with fabric via agent command %s",
Packit Service a04d08
                  self.ds_cfg['agent_command'])
Packit Service a04d08
        try:
Packit Service a04d08
            fabric_data = metadata_func()
Packit Service a04d08
        except Exception as e:
Packit Service a04d08
            report_diagnostic_event(
Packit Service a04d08
                "Error communicating with Azure fabric; You may experience "
Packit Service a04d08
                "connectivity issues: %s" % e)
Packit Service a04d08
            LOG.warning(
Packit Service a04d08
                "Error communicating with Azure fabric; You may experience "
Packit Service a04d08
                "connectivity issues.", exc_info=True)
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        util.del_file(REPORTED_READY_MARKER_FILE)
Packit Service a04d08
        util.del_file(REPROVISION_MARKER_FILE)
Packit Service a04d08
        return fabric_data
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def activate(self, cfg, is_new_instance):
Packit Service a04d08
        try:
Packit Service a04d08
            address_ephemeral_resize(is_new_instance=is_new_instance,
Packit Service a04d08
                                     preserve_ntfs=self.ds_cfg.get(
Packit Service a04d08
                                         DS_CFG_KEY_PRESERVE_NTFS, False))
Packit Service a04d08
        finally:
Packit Service a04d08
            push_log_to_kvp(self.sys_cfg['def_log_file'])
Packit Service a04d08
        return
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def availability_zone(self):
Packit Service a04d08
        return self.metadata.get(
Packit Service a04d08
            'imds', {}).get('compute', {}).get('platformFaultDomain')
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def network_config(self):
Packit Service a04d08
        """Generate a network config like net.generate_fallback_network() with
Packit Service a04d08
           the following exceptions.
Packit Service a04d08
Packit Service a04d08
           1. Probe the drivers of the net-devices present and inject them in
Packit Service a04d08
              the network configuration under params: driver: <driver> value
Packit Service a04d08
           2. Generate a fallback network config that does not include any of
Packit Service a04d08
              the blacklisted devices.
Packit Service a04d08
        """
Packit Service a04d08
        if not self._network_config or self._network_config == sources.UNSET:
Packit Service a04d08
            if self.ds_cfg.get('apply_network_config'):
Packit Service a04d08
                nc_src = self._metadata_imds
Packit Service a04d08
            else:
Packit Service a04d08
                nc_src = None
Packit Service a04d08
            self._network_config = parse_network_config(nc_src)
Packit Service a04d08
        return self._network_config
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def region(self):
Packit Service a04d08
        return self.metadata.get('imds', {}).get('compute', {}).get('location')
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _partitions_on_device(devpath, maxnum=16):
Packit Service a04d08
    # return a list of tuples (ptnum, path) for each part on devpath
Packit Service a04d08
    for suff in ("-part", "p", ""):
Packit Service a04d08
        found = []
Packit Service a04d08
        for pnum in range(1, maxnum):
Packit Service a04d08
            ppath = devpath + suff + str(pnum)
Packit Service a04d08
            if os.path.exists(ppath):
Packit Service a04d08
                found.append((pnum, os.path.realpath(ppath)))
Packit Service a04d08
        if found:
Packit Service a04d08
            return found
Packit Service a04d08
    return []
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def _has_ntfs_filesystem(devpath):
Packit Service a04d08
    ntfs_devices = util.find_devs_with("TYPE=ntfs", no_cache=True)
Packit Service a04d08
    LOG.debug('ntfs_devices found = %s', ntfs_devices)
Packit Service a04d08
    return os.path.realpath(devpath) in ntfs_devices
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def can_dev_be_reformatted(devpath, preserve_ntfs):
Packit Service a04d08
    """Determine if the ephemeral drive at devpath should be reformatted.
Packit Service a04d08
Packit Service a04d08
    A fresh ephemeral disk is formatted by Azure and will:
Packit Service a04d08
      a.) have a partition table (dos or gpt)
Packit Service a04d08
      b.) have 1 partition that is ntfs formatted, or
Packit Service a04d08
          have 2 partitions with the second partition ntfs formatted.
Packit Service a04d08
          (larger instances with >2TB ephemeral disk have gpt, and will
Packit Service a04d08
           have a microsoft reserved partition as part 1.  LP: #1686514)
Packit Service a04d08
      c.) the ntfs partition will have no files other than possibly
Packit Service a04d08
          'dataloss_warning_readme.txt'
Packit Service a04d08
Packit Service a04d08
    User can indicate that NTFS should never be destroyed by setting
Packit Service a04d08
    DS_CFG_KEY_PRESERVE_NTFS in dscfg.
Packit Service a04d08
    If data is found on NTFS, user is warned to set DS_CFG_KEY_PRESERVE_NTFS
Packit Service a04d08
    to make sure cloud-init does not accidentally wipe their data.
Packit Service a04d08
    If cloud-init cannot mount the disk to check for data, destruction
Packit Service a04d08
    will be allowed, unless the dscfg key is set."""
Packit Service a04d08
    if preserve_ntfs:
Packit Service a04d08
        msg = ('config says to never destroy NTFS (%s.%s), skipping checks' %
Packit Service a04d08
               (".".join(DS_CFG_PATH), DS_CFG_KEY_PRESERVE_NTFS))
Packit Service a04d08
        return False, msg
Packit Service a04d08
Packit Service a04d08
    if not os.path.exists(devpath):
Packit Service a04d08
        return False, 'device %s does not exist' % devpath
Packit Service a04d08
Packit Service a04d08
    LOG.debug('Resolving realpath of %s -> %s', devpath,
Packit Service a04d08
              os.path.realpath(devpath))
Packit Service a04d08
Packit Service a04d08
    # devpath of /dev/sd[a-z] or /dev/disk/cloud/azure_resource
Packit Service a04d08
    # where partitions are "<devpath>1" or "<devpath>-part1" or "<devpath>p1"
Packit Service a04d08
    partitions = _partitions_on_device(devpath)
Packit Service a04d08
    if len(partitions) == 0:
Packit Service a04d08
        return False, 'device %s was not partitioned' % devpath
Packit Service a04d08
    elif len(partitions) > 2:
Packit Service a04d08
        msg = ('device %s had 3 or more partitions: %s' %
Packit Service a04d08
               (devpath, ' '.join([p[1] for p in partitions])))
Packit Service a04d08
        return False, msg
Packit Service a04d08
    elif len(partitions) == 2:
Packit Service a04d08
        cand_part, cand_path = partitions[1]
Packit Service a04d08
    else:
Packit Service a04d08
        cand_part, cand_path = partitions[0]
Packit Service a04d08
Packit Service a04d08
    if not _has_ntfs_filesystem(cand_path):
Packit Service a04d08
        msg = ('partition %s (%s) on device %s was not ntfs formatted' %
Packit Service a04d08
               (cand_part, cand_path, devpath))
Packit Service a04d08
        return False, msg
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def count_files(mp):
Packit Service a04d08
        ignored = set(['dataloss_warning_readme.txt'])
Packit Service a04d08
        return len([f for f in os.listdir(mp) if f.lower() not in ignored])
Packit Service a04d08
Packit Service a04d08
    bmsg = ('partition %s (%s) on device %s was ntfs formatted' %
Packit Service a04d08
            (cand_part, cand_path, devpath))
Packit Service a04d08
Packit Service a04d08
    with events.ReportEventStack(
Packit Service a04d08
        name="mount-ntfs-and-count",
Packit Service a04d08
        description="mount-ntfs-and-count",
Packit Service a04d08
        parent=azure_ds_reporter
Packit Service a04d08
    ) as evt:
Packit Service a04d08
        try:
Packit Service a04d08
            file_count = util.mount_cb(cand_path, count_files, mtype="ntfs",
Packit Service a04d08
                                       update_env_for_mount={'LANG': 'C'})
Packit Service a04d08
        except util.MountFailedError as e:
Packit Service a04d08
            evt.description = "cannot mount ntfs"
Packit Service a04d08
            if "unknown filesystem type 'ntfs'" in str(e):
Packit Service a04d08
                return True, (bmsg + ' but this system cannot mount NTFS,'
Packit Service a04d08
                              ' assuming there are no important files.'
Packit Service a04d08
                              ' Formatting allowed.')
Packit Service a04d08
            return False, bmsg + ' but mount of %s failed: %s' % (cand_part, e)
Packit Service a04d08
Packit Service a04d08
        if file_count != 0:
Packit Service a04d08
            evt.description = "mounted and counted %d files" % file_count
Packit Service a04d08
            LOG.warning("it looks like you're using NTFS on the ephemeral"
Packit Service a04d08
                        " disk, to ensure that filesystem does not get wiped,"
Packit Service a04d08
                        " set %s.%s in config", '.'.join(DS_CFG_PATH),
Packit Service a04d08
                        DS_CFG_KEY_PRESERVE_NTFS)
Packit Service a04d08
            return False, bmsg + ' but had %d files on it.' % file_count
Packit Service a04d08
Packit Service a04d08
    return True, bmsg + ' and had no important files. Safe for reformatting.'
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120,
Packit Service a04d08
                             is_new_instance=False, preserve_ntfs=False):
Packit Service a04d08
    # wait for ephemeral disk to come up
Packit Service a04d08
    naplen = .2
Packit Service a04d08
    with events.ReportEventStack(
Packit Service a04d08
        name="wait-for-ephemeral-disk",
Packit Service a04d08
        description="wait for ephemeral disk",
Packit Service a04d08
        parent=azure_ds_reporter
Packit Service a04d08
    ):
Packit Service a04d08
        missing = util.wait_for_files([devpath],
Packit Service a04d08
                                      maxwait=maxwait,
Packit Service a04d08
                                      naplen=naplen,
Packit Service a04d08
                                      log_pre="Azure ephemeral disk: ")
Packit Service a04d08
Packit Service a04d08
        if missing:
Packit Service a04d08
            LOG.warning("ephemeral device '%s' did"
Packit Service a04d08
                        " not appear after %d seconds.",
Packit Service a04d08
                        devpath, maxwait)
Packit Service a04d08
            return
Packit Service a04d08
Packit Service a04d08
    result = False
Packit Service a04d08
    msg = None
Packit Service a04d08
    if is_new_instance:
Packit Service a04d08
        result, msg = (True, "First instance boot.")
Packit Service a04d08
    else:
Packit Service a04d08
        result, msg = can_dev_be_reformatted(devpath, preserve_ntfs)
Packit Service a04d08
Packit Service a04d08
    LOG.debug("reformattable=%s: %s", result, msg)
Packit Service a04d08
    if not result:
Packit Service a04d08
        return
Packit Service a04d08
Packit Service a04d08
    for mod in ['disk_setup', 'mounts']:
Packit Service a04d08
        sempath = '/var/lib/cloud/instance/sem/config_' + mod
Packit Service a04d08
        bmsg = 'Marker "%s" for module "%s"' % (sempath, mod)
Packit Service a04d08
        if os.path.exists(sempath):
Packit Service a04d08
            try:
Packit Service a04d08
                os.unlink(sempath)
Packit Service a04d08
                LOG.debug('%s removed.', bmsg)
Packit Service a04d08
            except Exception as e:
Packit Service a04d08
                # python3 throws FileNotFoundError, python2 throws OSError
Packit Service a04d08
                LOG.warning('%s: remove failed! (%s)', bmsg, e)
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.debug('%s did not exist.', bmsg)
Packit Service a04d08
    return
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def perform_hostname_bounce(hostname, cfg, prev_hostname):
Packit Service a04d08
    # set the hostname to 'hostname' if it is not already set to that.
Packit Service a04d08
    # then, if policy is not off, bounce the interface using command
Packit Service a04d08
    # Returns True if the network was bounced, False otherwise.
Packit Service a04d08
    command = cfg['command']
Packit Service a04d08
    interface = cfg['interface']
Packit Service a04d08
    policy = cfg['policy']
Packit Service a04d08
Packit Service a04d08
    msg = ("hostname=%s policy=%s interface=%s" %
Packit Service a04d08
           (hostname, policy, interface))
Packit Service a04d08
    env = os.environ.copy()
Packit Service a04d08
    env['interface'] = interface
Packit Service a04d08
    env['hostname'] = hostname
Packit Service a04d08
    env['old_hostname'] = prev_hostname
Packit Service a04d08
Packit Service a04d08
    if command == "builtin":
Packit Service a04d08
        if util.is_FreeBSD():
Packit Service a04d08
            command = BOUNCE_COMMAND_FREEBSD
Packit Service a04d08
        elif subp.which('ifup'):
Packit Service a04d08
            command = BOUNCE_COMMAND_IFUP
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.debug(
Packit Service a04d08
                "Skipping network bounce: ifupdown utils aren't present.")
Packit Service a04d08
            # Don't bounce as networkd handles hostname DDNS updates
Packit Service a04d08
            return False
Packit Service a04d08
    LOG.debug("pubhname: publishing hostname [%s]", msg)
Packit Service a04d08
    shell = not isinstance(command, (list, tuple))
Packit Service a04d08
    # capture=False, see comments in bug 1202758 and bug 1206164.
Packit Service a04d08
    util.log_time(logfunc=LOG.debug, msg="publishing hostname",
Packit Service a04d08
                  get_uptime=True, func=subp.subp,
Packit Service a04d08
                  kwargs={'args': command, 'shell': shell, 'capture': False,
Packit Service a04d08
                          'env': env})
Packit Service a04d08
    return True
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def crtfile_to_pubkey(fname, data=None):
Packit Service a04d08
    pipeline = ('openssl x509 -noout -pubkey < "$0" |'
Packit Service a04d08
                'ssh-keygen -i -m PKCS8 -f /dev/stdin')
Packit Service a04d08
    (out, _err) = subp.subp(['sh', '-c', pipeline, fname],
Packit Service a04d08
                            capture=True, data=data)
Packit Service a04d08
    return out.rstrip()
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def pubkeys_from_crt_files(flist):
Packit Service a04d08
    pubkeys = []
Packit Service a04d08
    errors = []
Packit Service a04d08
    for fname in flist:
Packit Service a04d08
        try:
Packit Service a04d08
            pubkeys.append(crtfile_to_pubkey(fname))
Packit Service a04d08
        except subp.ProcessExecutionError:
Packit Service a04d08
            errors.append(fname)
Packit Service a04d08
Packit Service a04d08
    if errors:
Packit Service a04d08
        LOG.warning("failed to convert the crt files to pubkey: %s", errors)
Packit Service a04d08
Packit Service a04d08
    return pubkeys
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def write_files(datadir, files, dirmode=None):
Packit Service a04d08
Packit Service a04d08
    def _redact_password(cnt, fname):
Packit Service a04d08
        """Azure provides the UserPassword in plain text. So we redact it"""
Packit Service a04d08
        try:
Packit Service a04d08
            root = ET.fromstring(cnt)
Packit Service a04d08
            for elem in root.iter():
Packit Service a04d08
                if ('UserPassword' in elem.tag and
Packit Service a04d08
                   elem.text != DEF_PASSWD_REDACTION):
Packit Service a04d08
                    elem.text = DEF_PASSWD_REDACTION
Packit Service a04d08
            return ET.tostring(root)
Packit Service a04d08
        except Exception:
Packit Service a04d08
            LOG.critical("failed to redact userpassword in %s", fname)
Packit Service a04d08
            return cnt
Packit Service a04d08
Packit Service a04d08
    if not datadir:
Packit Service a04d08
        return
Packit Service a04d08
    if not files:
Packit Service a04d08
        files = {}
Packit Service a04d08
    util.ensure_dir(datadir, dirmode)
Packit Service a04d08
    for (name, content) in files.items():
Packit Service a04d08
        fname = os.path.join(datadir, name)
Packit Service a04d08
        if 'ovf-env.xml' in name:
Packit Service a04d08
            content = _redact_password(content, fname)
Packit Service a04d08
        util.write_file(filename=fname, content=content, mode=0o600)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def invoke_agent(cmd):
Packit Service a04d08
    # this is a function itself to simplify patching it for test
Packit Service a04d08
    if cmd:
Packit Service a04d08
        LOG.debug("invoking agent: %s", cmd)
Packit Service a04d08
        subp.subp(cmd, shell=(not isinstance(cmd, list)))
Packit Service a04d08
    else:
Packit Service a04d08
        LOG.debug("not invoking agent")
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def find_child(node, filter_func):
Packit Service a04d08
    ret = []
Packit Service a04d08
    if not node.hasChildNodes():
Packit Service a04d08
        return ret
Packit Service a04d08
    for child in node.childNodes:
Packit Service a04d08
        if filter_func(child):
Packit Service a04d08
            ret.append(child)
Packit Service a04d08
    return ret
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def load_azure_ovf_pubkeys(sshnode):
Packit Service a04d08
    # This parses a 'SSH' node formatted like below, and returns
Packit Service a04d08
    # an array of dicts.
Packit Service a04d08
    #  [{'fingerprint': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',
Packit Service a04d08
    #    'path': '/where/to/go'}]
Packit Service a04d08
    #
Packit Service a04d08
    # <SSH><PublicKeys>
Packit Service a04d08
    #   <PublicKey><Fingerprint>ABC</FingerPrint><Path>/x/y/z</Path>
Packit Service a04d08
    #   ...
Packit Service a04d08
    # </PublicKeys></SSH>
Packit Service a04d08
    # Under some circumstances, there may be a <Value> element along with the
Packit Service a04d08
    # Fingerprint and Path. Pass those along if they appear.
Packit Service a04d08
    results = find_child(sshnode, lambda n: n.localName == "PublicKeys")
Packit Service a04d08
    if len(results) == 0:
Packit Service a04d08
        return []
Packit Service a04d08
    if len(results) > 1:
Packit Service a04d08
        raise BrokenAzureDataSource("Multiple 'PublicKeys'(%s) in SSH node" %
Packit Service a04d08
                                    len(results))
Packit Service a04d08
Packit Service a04d08
    pubkeys_node = results[0]
Packit Service a04d08
    pubkeys = find_child(pubkeys_node, lambda n: n.localName == "PublicKey")
Packit Service a04d08
Packit Service a04d08
    if len(pubkeys) == 0:
Packit Service a04d08
        return []
Packit Service a04d08
Packit Service a04d08
    found = []
Packit Service a04d08
    text_node = minidom.Document.TEXT_NODE
Packit Service a04d08
Packit Service a04d08
    for pk_node in pubkeys:
Packit Service a04d08
        if not pk_node.hasChildNodes():
Packit Service a04d08
            continue
Packit Service a04d08
Packit Service a04d08
        cur = {'fingerprint': "", 'path': "", 'value': ""}
Packit Service a04d08
        for child in pk_node.childNodes:
Packit Service a04d08
            if child.nodeType == text_node or not child.localName:
Packit Service a04d08
                continue
Packit Service a04d08
Packit Service a04d08
            name = child.localName.lower()
Packit Service a04d08
Packit Service a04d08
            if name not in cur.keys():
Packit Service a04d08
                continue
Packit Service a04d08
Packit Service a04d08
            if (len(child.childNodes) != 1 or
Packit Service a04d08
                    child.childNodes[0].nodeType != text_node):
Packit Service a04d08
                continue
Packit Service a04d08
Packit Service a04d08
            cur[name] = child.childNodes[0].wholeText.strip()
Packit Service a04d08
        found.append(cur)
Packit Service a04d08
Packit Service a04d08
    return found
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def read_azure_ovf(contents):
Packit Service a04d08
    try:
Packit Service a04d08
        dom = minidom.parseString(contents)
Packit Service a04d08
    except Exception as e:
Packit Service a04d08
        error_str = "Invalid ovf-env.xml: %s" % e
Packit Service a04d08
        report_diagnostic_event(error_str)
Packit Service a04d08
        raise BrokenAzureDataSource(error_str) from e
Packit Service a04d08
Packit Service a04d08
    results = find_child(dom.documentElement,
Packit Service a04d08
                         lambda n: n.localName == "ProvisioningSection")
Packit Service a04d08
Packit Service a04d08
    if len(results) == 0:
Packit Service a04d08
        raise NonAzureDataSource("No ProvisioningSection")
Packit Service a04d08
    if len(results) > 1:
Packit Service a04d08
        raise BrokenAzureDataSource("found '%d' ProvisioningSection items" %
Packit Service a04d08
                                    len(results))
Packit Service a04d08
    provSection = results[0]
Packit Service a04d08
Packit Service a04d08
    lpcs_nodes = find_child(provSection,
Packit Service a04d08
                            lambda n:
Packit Service a04d08
                            n.localName == "LinuxProvisioningConfigurationSet")
Packit Service a04d08
Packit Service a04d08
    if len(lpcs_nodes) == 0:
Packit Service a04d08
        raise NonAzureDataSource("No LinuxProvisioningConfigurationSet")
Packit Service a04d08
    if len(lpcs_nodes) > 1:
Packit Service a04d08
        raise BrokenAzureDataSource("found '%d' %ss" %
Packit Service a04d08
                                    (len(lpcs_nodes),
Packit Service a04d08
                                     "LinuxProvisioningConfigurationSet"))
Packit Service a04d08
    lpcs = lpcs_nodes[0]
Packit Service a04d08
Packit Service a04d08
    if not lpcs.hasChildNodes():
Packit Service a04d08
        raise BrokenAzureDataSource("no child nodes of configuration set")
Packit Service a04d08
Packit Service a04d08
    md_props = 'seedfrom'
Packit Service a04d08
    md = {'azure_data': {}}
Packit Service a04d08
    cfg = {}
Packit Service a04d08
    ud = ""
Packit Service a04d08
    password = None
Packit Service a04d08
    username = None
Packit Service a04d08
Packit Service a04d08
    for child in lpcs.childNodes:
Packit Service a04d08
        if child.nodeType == dom.TEXT_NODE or not child.localName:
Packit Service a04d08
            continue
Packit Service a04d08
Packit Service a04d08
        name = child.localName.lower()
Packit Service a04d08
Packit Service a04d08
        simple = False
Packit Service a04d08
        value = ""
Packit Service a04d08
        if (len(child.childNodes) == 1 and
Packit Service a04d08
                child.childNodes[0].nodeType == dom.TEXT_NODE):
Packit Service a04d08
            simple = True
Packit Service a04d08
            value = child.childNodes[0].wholeText
Packit Service a04d08
Packit Service a04d08
        attrs = dict([(k, v) for k, v in child.attributes.items()])
Packit Service a04d08
Packit Service a04d08
        # we accept either UserData or CustomData.  If both are present
Packit Service a04d08
        # then behavior is undefined.
Packit Service a04d08
        if name == "userdata" or name == "customdata":
Packit Service a04d08
            if attrs.get('encoding') in (None, "base64"):
Packit Service a04d08
                ud = base64.b64decode(''.join(value.split()))
Packit Service a04d08
            else:
Packit Service a04d08
                ud = value
Packit Service a04d08
        elif name == "username":
Packit Service a04d08
            username = value
Packit Service a04d08
        elif name == "userpassword":
Packit Service a04d08
            password = value
Packit Service a04d08
        elif name == "hostname":
Packit Service a04d08
            md['local-hostname'] = value
Packit Service a04d08
        elif name == "dscfg":
Packit Service a04d08
            if attrs.get('encoding') in (None, "base64"):
Packit Service a04d08
                dscfg = base64.b64decode(''.join(value.split()))
Packit Service a04d08
            else:
Packit Service a04d08
                dscfg = value
Packit Service a04d08
            cfg['datasource'] = {DS_NAME: util.load_yaml(dscfg, default={})}
Packit Service a04d08
        elif name == "ssh":
Packit Service a04d08
            cfg['_pubkeys'] = load_azure_ovf_pubkeys(child)
Packit Service a04d08
        elif name == "disablesshpasswordauthentication":
Packit Service a04d08
            cfg['ssh_pwauth'] = util.is_false(value)
Packit Service a04d08
        elif simple:
Packit Service a04d08
            if name in md_props:
Packit Service a04d08
                md[name] = value
Packit Service a04d08
            else:
Packit Service a04d08
                md['azure_data'][name] = value
Packit Service a04d08
Packit Service a04d08
    defuser = {}
Packit Service a04d08
    if username:
Packit Service a04d08
        defuser['name'] = username
Packit Service a04d08
    if password:
Packit Service a04d08
        defuser['lock_passwd'] = False
Packit Service a04d08
        if DEF_PASSWD_REDACTION != password:
Packit Service a04d08
            defuser['passwd'] = encrypt_pass(password)
Packit Service a04d08
Packit Service a04d08
    if defuser:
Packit Service a04d08
        cfg['system_info'] = {'default_user': defuser}
Packit Service a04d08
Packit Service a04d08
    if 'ssh_pwauth' not in cfg and password:
Packit Service a04d08
        cfg['ssh_pwauth'] = True
Packit Service a04d08
Packit Service a04d08
    cfg['PreprovisionedVm'] = _extract_preprovisioned_vm_setting(dom)
Packit Service a04d08
Packit Service a04d08
    return (md, ud, cfg)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def _extract_preprovisioned_vm_setting(dom):
Packit Service a04d08
    """Read the preprovision flag from the ovf. It should not
Packit Service a04d08
       exist unless true."""
Packit Service a04d08
    platform_settings_section = find_child(
Packit Service a04d08
        dom.documentElement,
Packit Service a04d08
        lambda n: n.localName == "PlatformSettingsSection")
Packit Service a04d08
    if not platform_settings_section or len(platform_settings_section) == 0:
Packit Service a04d08
        LOG.debug("PlatformSettingsSection not found")
Packit Service a04d08
        return False
Packit Service a04d08
    platform_settings = find_child(
Packit Service a04d08
        platform_settings_section[0],
Packit Service a04d08
        lambda n: n.localName == "PlatformSettings")
Packit Service a04d08
    if not platform_settings or len(platform_settings) == 0:
Packit Service a04d08
        LOG.debug("PlatformSettings not found")
Packit Service a04d08
        return False
Packit Service a04d08
    preprovisionedVm = find_child(
Packit Service a04d08
        platform_settings[0],
Packit Service a04d08
        lambda n: n.localName == "PreprovisionedVm")
Packit Service a04d08
    if not preprovisionedVm or len(preprovisionedVm) == 0:
Packit Service a04d08
        LOG.debug("PreprovisionedVm not found")
Packit Service a04d08
        return False
Packit Service a04d08
    return util.translate_bool(preprovisionedVm[0].firstChild.nodeValue)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def encrypt_pass(password, salt_id="$6$"):
Packit Service a04d08
    return crypt.crypt(password, salt_id + util.rand_str(strlen=16))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def _check_freebsd_cdrom(cdrom_dev):
Packit Service a04d08
    """Return boolean indicating path to cdrom device has content."""
Packit Service a04d08
    try:
Packit Service a04d08
        with open(cdrom_dev) as fp:
Packit Service a04d08
            fp.read(1024)
Packit Service a04d08
            return True
Packit Service a04d08
    except IOError:
Packit Service a04d08
        LOG.debug("cdrom (%s) is not configured", cdrom_dev)
Packit Service a04d08
    return False
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def _get_random_seed(source=PLATFORM_ENTROPY_SOURCE):
Packit Service a04d08
    """Return content random seed file if available, otherwise,
Packit Service a04d08
       return None."""
Packit Service a04d08
    # azure / hyper-v provides random data here
Packit Service a04d08
    # now update ds_cfg to reflect contents pass in config
Packit Service a04d08
    if source is None:
Packit Service a04d08
        return None
Packit Service a04d08
    seed = util.load_file(source, quiet=True, decode=False)
Packit Service a04d08
Packit Service a04d08
    # The seed generally contains non-Unicode characters. load_file puts
Packit Service a04d08
    # them into a str (in python 2) or bytes (in python 3). In python 2,
Packit Service a04d08
    # bad octets in a str cause util.json_dumps() to throw an exception. In
Packit Service a04d08
    # python 3, bytes is a non-serializable type, and the handler load_file
Packit Service a04d08
    # uses applies b64 encoding *again* to handle it. The simplest solution
Packit Service a04d08
    # is to just b64encode the data and then decode it to a serializable
Packit Service a04d08
    # string. Same number of bits of entropy, just with 25% more zeroes.
Packit Service a04d08
    # There's no need to undo this base64-encoding when the random seed is
Packit Service a04d08
    # actually used in cc_seed_random.py.
Packit Service a04d08
    seed = base64.b64encode(seed).decode()
Packit Service a04d08
Packit Service a04d08
    return seed
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def list_possible_azure_ds_devs():
Packit Service a04d08
    devlist = []
Packit Service a04d08
    if util.is_FreeBSD():
Packit Service a04d08
        cdrom_dev = "/dev/cd0"
Packit Service a04d08
        if _check_freebsd_cdrom(cdrom_dev):
Packit Service a04d08
            return [cdrom_dev]
Packit Service a04d08
    else:
Packit Service a04d08
        for fstype in ("iso9660", "udf"):
Packit Service a04d08
            devlist.extend(util.find_devs_with("TYPE=%s" % fstype))
Packit Service a04d08
Packit Service a04d08
    devlist.sort(reverse=True)
Packit Service a04d08
    return devlist
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def load_azure_ds_dir(source_dir):
Packit Service a04d08
    ovf_file = os.path.join(source_dir, "ovf-env.xml")
Packit Service a04d08
Packit Service a04d08
    if not os.path.isfile(ovf_file):
Packit Service a04d08
        raise NonAzureDataSource("No ovf-env file found")
Packit Service a04d08
Packit Service a04d08
    with open(ovf_file, "rb") as fp:
Packit Service a04d08
        contents = fp.read()
Packit Service a04d08
Packit Service a04d08
    md, ud, cfg = read_azure_ovf(contents)
Packit Service a04d08
    return (md, ud, cfg, {'ovf-env.xml': contents})
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def parse_network_config(imds_metadata):
Packit Service a04d08
    """Convert imds_metadata dictionary to network v2 configuration.
Packit Service a04d08
Packit Service a04d08
    Parses network configuration from imds metadata if present or generate
Packit Service a04d08
    fallback network config excluding mlx4_core devices.
Packit Service a04d08
Packit Service a04d08
    @param: imds_metadata: Dict of content read from IMDS network service.
Packit Service a04d08
    @return: Dictionary containing network version 2 standard configuration.
Packit Service a04d08
    """
Packit Service a04d08
    with events.ReportEventStack(
Packit Service a04d08
        name="parse_network_config",
Packit Service a04d08
        description="",
Packit Service a04d08
        parent=azure_ds_reporter
Packit Service a04d08
    ) as evt:
Packit Service a04d08
        if imds_metadata != sources.UNSET and imds_metadata:
Packit Service a04d08
            netconfig = {'version': 2, 'ethernets': {}}
Packit Service a04d08
            LOG.debug('Azure: generating network configuration from IMDS')
Packit Service a04d08
            network_metadata = imds_metadata['network']
Packit Service a04d08
            for idx, intf in enumerate(network_metadata['interface']):
Packit Service a04d08
                # First IPv4 and/or IPv6 address will be obtained via DHCP.
Packit Service a04d08
                # Any additional IPs of each type will be set as static
Packit Service a04d08
                # addresses.
Packit Service a04d08
                nicname = 'eth{idx}'.format(idx=idx)
Packit Service a04d08
                dhcp_override = {'route-metric': (idx + 1) * 100}
Packit Service a04d08
                dev_config = {'dhcp4': True, 'dhcp4-overrides': dhcp_override,
Packit Service a04d08
                              'dhcp6': False}
Packit Service a04d08
                for addr_type in ('ipv4', 'ipv6'):
Packit Service a04d08
                    addresses = intf.get(addr_type, {}).get('ipAddress', [])
Packit Service a04d08
                    if addr_type == 'ipv4':
Packit Service a04d08
                        default_prefix = '24'
Packit Service a04d08
                    else:
Packit Service a04d08
                        default_prefix = '128'
Packit Service a04d08
                        if addresses:
Packit Service a04d08
                            dev_config['dhcp6'] = True
Packit Service a04d08
                            # non-primary interfaces should have a higher
Packit Service a04d08
                            # route-metric (cost) so default routes prefer
Packit Service a04d08
                            # primary nic due to lower route-metric value
Packit Service a04d08
                            dev_config['dhcp6-overrides'] = dhcp_override
Packit Service a04d08
                    for addr in addresses[1:]:
Packit Service a04d08
                        # Append static address config for ip > 1
Packit Service a04d08
                        netPrefix = intf[addr_type]['subnet'][0].get(
Packit Service a04d08
                            'prefix', default_prefix)
Packit Service a04d08
                        privateIp = addr['privateIpAddress']
Packit Service a04d08
                        if not dev_config.get('addresses'):
Packit Service a04d08
                            dev_config['addresses'] = []
Packit Service a04d08
                        dev_config['addresses'].append(
Packit Service a04d08
                            '{ip}/{prefix}'.format(
Packit Service a04d08
                                ip=privateIp, prefix=netPrefix))
Packit Service a04d08
                if dev_config:
Packit Service a04d08
                    mac = ':'.join(re.findall(r'..', intf['macAddress']))
Packit Service a04d08
                    dev_config.update({
Packit Service a04d08
                        'match': {'macaddress': mac.lower()},
Packit Service a04d08
                        'set-name': nicname
Packit Service a04d08
                    })
Packit Service a04d08
                    # With netvsc, we can get two interfaces that
Packit Service a04d08
                    # share the same MAC, so we need to make sure
Packit Service a04d08
                    # our match condition also contains the driver
Packit Service a04d08
                    driver = device_driver(nicname)
Packit Service a04d08
                    if driver and driver == 'hv_netvsc':
Packit Service a04d08
                        dev_config['match']['driver'] = driver
Packit Service a04d08
                    netconfig['ethernets'][nicname] = dev_config
Packit Service a04d08
            evt.description = "network config from imds"
Packit Service a04d08
        else:
Packit Service a04d08
            blacklist = ['mlx4_core']
Packit Service a04d08
            LOG.debug('Azure: generating fallback configuration')
Packit Service a04d08
            # generate a network config, blacklist picking mlx4_core devs
Packit Service a04d08
            netconfig = net.generate_fallback_config(
Packit Service a04d08
                blacklist_drivers=blacklist, config_driver=True)
Packit Service a04d08
            evt.description = "network config from fallback"
Packit Service a04d08
        return netconfig
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def get_metadata_from_imds(fallback_nic, retries):
Packit Service a04d08
    """Query Azure's network metadata service, returning a dictionary.
Packit Service a04d08
Packit Service a04d08
    If network is not up, setup ephemeral dhcp on fallback_nic to talk to the
Packit Service a04d08
    IMDS. For more info on IMDS:
Packit Service a04d08
        https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
Packit Service a04d08
Packit Service a04d08
    @param fallback_nic: String. The name of the nic which requires active
Packit Service a04d08
        network in order to query IMDS.
Packit Service a04d08
    @param retries: The number of retries of the IMDS_URL.
Packit Service a04d08
Packit Service a04d08
    @return: A dict of instance metadata containing compute and network
Packit Service a04d08
        info.
Packit Service a04d08
    """
Packit Service a04d08
    kwargs = {'logfunc': LOG.debug,
Packit Service a04d08
              'msg': 'Crawl of Azure Instance Metadata Service (IMDS)',
Packit Service a04d08
              'func': _get_metadata_from_imds, 'args': (retries,)}
Packit Service a04d08
    if net.is_up(fallback_nic):
Packit Service a04d08
        return util.log_time(**kwargs)
Packit Service a04d08
    else:
Packit Service a04d08
        try:
Packit Service a04d08
            with EphemeralDHCPv4WithReporting(
Packit Service a04d08
                    azure_ds_reporter, fallback_nic):
Packit Service a04d08
                return util.log_time(**kwargs)
Packit Service a04d08
        except Exception as e:
Packit Service a04d08
            report_diagnostic_event("exception while getting metadata: %s" % e)
Packit Service a04d08
            raise
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def _get_metadata_from_imds(retries):
Packit Service a04d08
Packit Service a04d08
    url = IMDS_URL + "instance?api-version=2017-12-01"
Packit Service a04d08
    headers = {"Metadata": "true"}
Packit Service a04d08
    try:
Packit Service a04d08
        response = readurl(
Packit Service a04d08
            url, timeout=IMDS_TIMEOUT_IN_SECONDS, headers=headers,
Packit Service a04d08
            retries=retries, exception_cb=retry_on_url_exc)
Packit Service a04d08
    except Exception as e:
Packit Service a04d08
        msg = 'Ignoring IMDS instance metadata: %s' % e
Packit Service a04d08
        report_diagnostic_event(msg)
Packit Service a04d08
        LOG.debug(msg)
Packit Service a04d08
        return {}
Packit Service a04d08
    try:
Packit Service a04d08
        from json.decoder import JSONDecodeError
Packit Service a04d08
        json_decode_error = JSONDecodeError
Packit Service a04d08
    except ImportError:
Packit Service a04d08
        json_decode_error = ValueError
Packit Service a04d08
Packit Service a04d08
    try:
Packit Service a04d08
        return util.load_json(str(response))
Packit Service a04d08
    except json_decode_error as e:
Packit Service a04d08
        report_diagnostic_event('non-json imds response' % e)
Packit Service a04d08
        LOG.warning(
Packit Service a04d08
            'Ignoring non-json IMDS instance metadata: %s', str(response))
Packit Service a04d08
    return {}
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def maybe_remove_ubuntu_network_config_scripts(paths=None):
Packit Service a04d08
    """Remove Azure-specific ubuntu network config for non-primary nics.
Packit Service a04d08
Packit Service a04d08
    @param paths: List of networking scripts or directories to remove when
Packit Service a04d08
        present.
Packit Service a04d08
Packit Service a04d08
    In certain supported ubuntu images, static udev rules or netplan yaml
Packit Service a04d08
    config is delivered in the base ubuntu image to support dhcp on any
Packit Service a04d08
    additional interfaces which get attached by a customer at some point
Packit Service a04d08
    after initial boot. Since the Azure datasource can now regenerate
Packit Service a04d08
    network configuration as metadata reports these new devices, we no longer
Packit Service a04d08
    want the udev rules or netplan's 90-hotplug-azure.yaml to configure
Packit Service a04d08
    networking on eth1 or greater as it might collide with cloud-init's
Packit Service a04d08
    configuration.
Packit Service a04d08
Packit Service a04d08
    Remove the any existing extended network scripts if the datasource is
Packit Service a04d08
    enabled to write network per-boot.
Packit Service a04d08
    """
Packit Service a04d08
    if not paths:
Packit Service a04d08
        paths = UBUNTU_EXTENDED_NETWORK_SCRIPTS
Packit Service a04d08
    logged = False
Packit Service a04d08
    for path in paths:
Packit Service a04d08
        if os.path.exists(path):
Packit Service a04d08
            if not logged:
Packit Service a04d08
                LOG.info(
Packit Service a04d08
                    'Removing Ubuntu extended network scripts because'
Packit Service a04d08
                    ' cloud-init updates Azure network configuration on the'
Packit Service a04d08
                    ' following event: %s.',
Packit Service a04d08
                    EventType.BOOT)
Packit Service a04d08
                logged = True
Packit Service a04d08
            if os.path.isdir(path):
Packit Service a04d08
                util.del_dir(path)
Packit Service a04d08
            else:
Packit Service a04d08
                util.del_file(path)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _is_platform_viable(seed_dir):
Packit Service a04d08
    """Check platform environment to report if this datasource may run."""
Packit Service a04d08
    with events.ReportEventStack(
Packit Service a04d08
        name="check-platform-viability",
Packit Service a04d08
        description="found azure asset tag",
Packit Service a04d08
        parent=azure_ds_reporter
Packit Service a04d08
    ) as evt:
Packit Service a04d08
        asset_tag = util.read_dmi_data('chassis-asset-tag')
Packit Service a04d08
        if asset_tag == AZURE_CHASSIS_ASSET_TAG:
Packit Service a04d08
            return True
Packit Service a04d08
        msg = "Non-Azure DMI asset tag '%s' discovered." % asset_tag
Packit Service a04d08
        LOG.debug(msg)
Packit Service a04d08
        evt.description = msg
Packit Service a04d08
        report_diagnostic_event(msg)
Packit Service a04d08
        if os.path.exists(os.path.join(seed_dir, 'ovf-env.xml')):
Packit Service a04d08
            return True
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class BrokenAzureDataSource(Exception):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class NonAzureDataSource(Exception):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Legacy: Must be present in case we load an old pkl object
Packit Service a04d08
DataSourceAzureNet = DataSourceAzure
Packit Service a04d08
Packit Service a04d08
# Used to match classes to dependencies
Packit Service a04d08
datasources = [
Packit Service a04d08
    (DataSourceAzure, (sources.DEP_FILESYSTEM, )),
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