|
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
|