|
Packit Service |
a04d08 |
# Copyright (C) 2009-2010 Canonical Ltd.
|
|
Packit Service |
a04d08 |
# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
|
|
Packit Service |
a04d08 |
# Copyright (C) 2012 Yahoo! Inc.
|
|
Packit Service |
a04d08 |
#
|
|
Packit Service |
a04d08 |
# Author: Scott Moser <scott.moser@canonical.com>
|
|
Packit Service |
a04d08 |
# Author: Juerg Hafliger <juerg.haefliger@hp.com>
|
|
Packit Service |
a04d08 |
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
|
|
Packit Service |
a04d08 |
#
|
|
Packit Service |
a04d08 |
# This file is part of cloud-init. See LICENSE file for license information.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
import os
|
|
Packit Service |
a04d08 |
import time
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from cloudinit import ec2_utils as ec2
|
|
Packit Service |
a04d08 |
from cloudinit import log as logging
|
|
Packit Service |
a04d08 |
from cloudinit import net
|
|
Packit Service |
a04d08 |
from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError
|
|
Packit Service |
a04d08 |
from cloudinit import sources
|
|
Packit Service |
a04d08 |
from cloudinit import url_helper as uhelp
|
|
Packit Service |
a04d08 |
from cloudinit import util
|
|
Packit Service |
a04d08 |
from cloudinit import warnings
|
|
Packit Service |
a04d08 |
from cloudinit.event import EventType
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
LOG = logging.getLogger(__name__)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
SKIP_METADATA_URL_CODES = frozenset([uhelp.NOT_FOUND])
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
STRICT_ID_PATH = ("datasource", "Ec2", "strict_id")
|
|
Packit Service |
a04d08 |
STRICT_ID_DEFAULT = "warn"
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
API_TOKEN_ROUTE = 'latest/api/token'
|
|
Packit Service |
a04d08 |
AWS_TOKEN_TTL_SECONDS = '21600'
|
|
Packit Service |
9bfd13 |
AWS_TOKEN_PUT_HEADER = 'X-aws-ec2-metadata-token'
|
|
Packit Service |
9bfd13 |
AWS_TOKEN_REQ_HEADER = AWS_TOKEN_PUT_HEADER + '-ttl-seconds'
|
|
Packit Service |
9bfd13 |
AWS_TOKEN_REDACT = [AWS_TOKEN_PUT_HEADER, AWS_TOKEN_REQ_HEADER]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class CloudNames(object):
|
|
Packit Service |
a04d08 |
ALIYUN = "aliyun"
|
|
Packit Service |
a04d08 |
AWS = "aws"
|
|
Packit Service |
a04d08 |
BRIGHTBOX = "brightbox"
|
|
Packit Service |
a04d08 |
ZSTACK = "zstack"
|
|
Packit Service |
a04d08 |
E24CLOUD = "e24cloud"
|
|
Packit Service |
a04d08 |
# UNKNOWN indicates no positive id. If strict_id is 'warn' or 'false',
|
|
Packit Service |
a04d08 |
# then an attempt at the Ec2 Metadata service will be made.
|
|
Packit Service |
a04d08 |
UNKNOWN = "unknown"
|
|
Packit Service |
a04d08 |
# NO_EC2_METADATA indicates this platform does not have a Ec2 metadata
|
|
Packit Service |
a04d08 |
# service available. No attempt at the Ec2 Metadata service will be made.
|
|
Packit Service |
a04d08 |
NO_EC2_METADATA = "no-ec2-metadata"
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class DataSourceEc2(sources.DataSource):
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
dsname = 'Ec2'
|
|
Packit Service |
a04d08 |
# Default metadata urls that will be used if none are provided
|
|
Packit Service |
a04d08 |
# They will be checked for 'resolveability' and some of the
|
|
Packit Service |
a04d08 |
# following may be discarded if they do not resolve
|
|
Packit Service |
a04d08 |
metadata_urls = ["http://169.254.169.254", "http://instance-data.:8773"]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# The minimum supported metadata_version from the ec2 metadata apis
|
|
Packit Service |
a04d08 |
min_metadata_version = '2009-04-04'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Priority ordered list of additional metadata versions which will be tried
|
|
Packit Service |
a04d08 |
# for extended metadata content. IPv6 support comes in 2016-09-02
|
|
Packit Service |
9bfd13 |
extended_metadata_versions = ['2018-09-24', '2016-09-02']
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Setup read_url parameters per get_url_params.
|
|
Packit Service |
a04d08 |
url_max_wait = 120
|
|
Packit Service |
a04d08 |
url_timeout = 50
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
_api_token = None # API token for accessing the metadata service
|
|
Packit Service |
a04d08 |
_network_config = sources.UNSET # Used to cache calculated network cfg v1
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Whether we want to get network configuration from the metadata service.
|
|
Packit Service |
a04d08 |
perform_dhcp_setup = False
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, sys_cfg, distro, paths):
|
|
Packit Service |
a04d08 |
super(DataSourceEc2, self).__init__(sys_cfg, distro, paths)
|
|
Packit Service |
a04d08 |
self.metadata_address = None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _get_cloud_name(self):
|
|
Packit Service |
a04d08 |
"""Return the cloud name as identified during _get_data."""
|
|
Packit Service |
a04d08 |
return identify_platform()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _get_data(self):
|
|
Packit Service |
a04d08 |
strict_mode, _sleep = read_strict_mode(
|
|
Packit Service |
a04d08 |
util.get_cfg_by_path(self.sys_cfg, STRICT_ID_PATH,
|
|
Packit Service |
a04d08 |
STRICT_ID_DEFAULT), ("warn", None))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
LOG.debug("strict_mode: %s, cloud_name=%s cloud_platform=%s",
|
|
Packit Service |
a04d08 |
strict_mode, self.cloud_name, self.platform)
|
|
Packit Service |
a04d08 |
if strict_mode == "true" and self.cloud_name == CloudNames.UNKNOWN:
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
elif self.cloud_name == CloudNames.NO_EC2_METADATA:
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if self.perform_dhcp_setup: # Setup networking in init-local stage.
|
|
Packit Service |
a04d08 |
if util.is_FreeBSD():
|
|
Packit Service |
a04d08 |
LOG.debug("FreeBSD doesn't support running dhclient with -sf")
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
with EphemeralDHCPv4(self.fallback_interface):
|
|
Packit Service |
a04d08 |
self._crawled_metadata = util.log_time(
|
|
Packit Service |
a04d08 |
logfunc=LOG.debug, msg='Crawl of metadata service',
|
|
Packit Service |
a04d08 |
func=self.crawl_metadata)
|
|
Packit Service |
a04d08 |
except NoDHCPLeaseError:
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
self._crawled_metadata = util.log_time(
|
|
Packit Service |
a04d08 |
logfunc=LOG.debug, msg='Crawl of metadata service',
|
|
Packit Service |
a04d08 |
func=self.crawl_metadata)
|
|
Packit Service |
a04d08 |
if not self._crawled_metadata:
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
self.metadata = self._crawled_metadata.get('meta-data', None)
|
|
Packit Service |
a04d08 |
self.userdata_raw = self._crawled_metadata.get('user-data', None)
|
|
Packit Service |
a04d08 |
self.identity = self._crawled_metadata.get(
|
|
Packit Service |
a04d08 |
'dynamic', {}).get('instance-identity', {}).get('document', {})
|
|
Packit Service |
a04d08 |
return True
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def is_classic_instance(self):
|
|
Packit Service |
a04d08 |
"""Report if this instance type is Ec2 Classic (non-vpc)."""
|
|
Packit Service |
a04d08 |
if not self.metadata:
|
|
Packit Service |
a04d08 |
# Can return False on inconclusive as we are also called in
|
|
Packit Service |
a04d08 |
# network_config where metadata will be present.
|
|
Packit Service |
a04d08 |
# Secondary call site is in packaging postinst script.
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
ifaces_md = self.metadata.get('network', {}).get('interfaces', {})
|
|
Packit Service |
a04d08 |
for _mac, mac_data in ifaces_md.get('macs', {}).items():
|
|
Packit Service |
a04d08 |
if 'vpc-id' in mac_data:
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
return True
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@property
|
|
Packit Service |
a04d08 |
def launch_index(self):
|
|
Packit Service |
a04d08 |
if not self.metadata:
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
return self.metadata.get('ami-launch-index')
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@property
|
|
Packit Service |
a04d08 |
def platform(self):
|
|
Packit Service |
a04d08 |
# Handle upgrade path of pickled ds
|
|
Packit Service |
a04d08 |
if not hasattr(self, '_platform_type'):
|
|
Packit Service |
a04d08 |
self._platform_type = DataSourceEc2.dsname.lower()
|
|
Packit Service |
a04d08 |
if not self._platform_type:
|
|
Packit Service |
a04d08 |
self._platform_type = DataSourceEc2.dsname.lower()
|
|
Packit Service |
a04d08 |
return self._platform_type
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_metadata_api_version(self):
|
|
Packit Service |
a04d08 |
"""Get the best supported api version from the metadata service.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Loop through all extended support metadata versions in order and
|
|
Packit Service |
a04d08 |
return the most-fully featured metadata api version discovered.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
If extended_metadata_versions aren't present, return the datasource's
|
|
Packit Service |
a04d08 |
min_metadata_version.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
# Assumes metadata service is already up
|
|
Packit Service |
a04d08 |
url_tmpl = '{0}/{1}/meta-data/instance-id'
|
|
Packit Service |
a04d08 |
headers = self._get_headers()
|
|
Packit Service |
a04d08 |
for api_ver in self.extended_metadata_versions:
|
|
Packit Service |
a04d08 |
url = url_tmpl.format(self.metadata_address, api_ver)
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
9bfd13 |
resp = uhelp.readurl(url=url, headers=headers,
|
|
Packit Service |
9bfd13 |
headers_redact=AWS_TOKEN_REDACT)
|
|
Packit Service |
a04d08 |
except uhelp.UrlError as e:
|
|
Packit Service |
a04d08 |
LOG.debug('url %s raised exception %s', url, e)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
if resp.code == 200:
|
|
Packit Service |
a04d08 |
LOG.debug('Found preferred metadata version %s', api_ver)
|
|
Packit Service |
a04d08 |
return api_ver
|
|
Packit Service |
a04d08 |
elif resp.code == 404:
|
|
Packit Service |
a04d08 |
msg = 'Metadata api version %s not present. Headers: %s'
|
|
Packit Service |
a04d08 |
LOG.debug(msg, api_ver, resp.headers)
|
|
Packit Service |
a04d08 |
return self.min_metadata_version
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_instance_id(self):
|
|
Packit Service |
a04d08 |
if self.cloud_name == CloudNames.AWS:
|
|
Packit Service |
a04d08 |
# Prefer the ID from the instance identity document, but fall back
|
|
Packit Service |
a04d08 |
if not getattr(self, 'identity', None):
|
|
Packit Service |
a04d08 |
# If re-using cached datasource, it's get_data run didn't
|
|
Packit Service |
a04d08 |
# setup self.identity. So we need to do that now.
|
|
Packit Service |
a04d08 |
api_version = self.get_metadata_api_version()
|
|
Packit Service |
a04d08 |
self.identity = ec2.get_instance_identity(
|
|
Packit Service |
a04d08 |
api_version, self.metadata_address,
|
|
Packit Service |
a04d08 |
headers_cb=self._get_headers,
|
|
Packit Service |
9bfd13 |
headers_redact=AWS_TOKEN_REDACT,
|
|
Packit Service |
a04d08 |
exception_cb=self._refresh_stale_aws_token_cb).get(
|
|
Packit Service |
a04d08 |
'document', {})
|
|
Packit Service |
a04d08 |
return self.identity.get(
|
|
Packit Service |
a04d08 |
'instanceId', self.metadata['instance-id'])
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
return self.metadata['instance-id']
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _maybe_fetch_api_token(self, mdurls, timeout=None, max_wait=None):
|
|
Packit Service |
9bfd13 |
""" Get an API token for EC2 Instance Metadata Service.
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
On EC2. IMDS will always answer an API token, unless
|
|
Packit Service |
9bfd13 |
the instance owner has disabled the IMDS HTTP endpoint or
|
|
Packit Service |
9bfd13 |
the network topology conflicts with the configured hop-limit.
|
|
Packit Service |
9bfd13 |
"""
|
|
Packit Service |
a04d08 |
if self.cloud_name != CloudNames.AWS:
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
urls = []
|
|
Packit Service |
a04d08 |
url2base = {}
|
|
Packit Service |
a04d08 |
url_path = API_TOKEN_ROUTE
|
|
Packit Service |
a04d08 |
request_method = 'PUT'
|
|
Packit Service |
a04d08 |
for url in mdurls:
|
|
Packit Service |
a04d08 |
cur = '{0}/{1}'.format(url, url_path)
|
|
Packit Service |
a04d08 |
urls.append(cur)
|
|
Packit Service |
a04d08 |
url2base[cur] = url
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
# use the self._imds_exception_cb to check for Read errors
|
|
Packit Service |
a04d08 |
LOG.debug('Fetching Ec2 IMDSv2 API Token')
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
response = None
|
|
Packit Service |
9bfd13 |
url = None
|
|
Packit Service |
9bfd13 |
url_params = self.get_url_params()
|
|
Packit Service |
9bfd13 |
try:
|
|
Packit Service |
9bfd13 |
url, response = uhelp.wait_for_url(
|
|
Packit Service |
9bfd13 |
urls=urls, max_wait=url_params.max_wait_seconds,
|
|
Packit Service |
9bfd13 |
timeout=url_params.timeout_seconds, status_cb=LOG.warning,
|
|
Packit Service |
9bfd13 |
headers_cb=self._get_headers,
|
|
Packit Service |
9bfd13 |
exception_cb=self._imds_exception_cb,
|
|
Packit Service |
9bfd13 |
request_method=request_method,
|
|
Packit Service |
9bfd13 |
headers_redact=AWS_TOKEN_REDACT)
|
|
Packit Service |
9bfd13 |
except uhelp.UrlError:
|
|
Packit Service |
9bfd13 |
# We use the raised exception to interupt the retry loop.
|
|
Packit Service |
9bfd13 |
# Nothing else to do here.
|
|
Packit Service |
9bfd13 |
pass
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if url and response:
|
|
Packit Service |
a04d08 |
self._api_token = response
|
|
Packit Service |
a04d08 |
return url2base[url]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
# If we get here, then wait_for_url timed out, waiting for IMDS
|
|
Packit Service |
9bfd13 |
# or the IMDS HTTP endpoint is disabled
|
|
Packit Service |
9bfd13 |
return None
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
a04d08 |
def wait_for_metadata_service(self):
|
|
Packit Service |
a04d08 |
mcfg = self.ds_cfg
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
url_params = self.get_url_params()
|
|
Packit Service |
a04d08 |
if url_params.max_wait_seconds <= 0:
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Remove addresses from the list that wont resolve.
|
|
Packit Service |
a04d08 |
mdurls = mcfg.get("metadata_urls", self.metadata_urls)
|
|
Packit Service |
a04d08 |
filtered = [x for x in mdurls if util.is_resolvable_url(x)]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if set(filtered) != set(mdurls):
|
|
Packit Service |
a04d08 |
LOG.debug("Removed the following from metadata urls: %s",
|
|
Packit Service |
a04d08 |
list((set(mdurls) - set(filtered))))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if len(filtered):
|
|
Packit Service |
a04d08 |
mdurls = filtered
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
LOG.warning("Empty metadata url list! using default list")
|
|
Packit Service |
a04d08 |
mdurls = self.metadata_urls
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# try the api token path first
|
|
Packit Service |
a04d08 |
metadata_address = self._maybe_fetch_api_token(mdurls)
|
|
Packit Service |
9bfd13 |
# When running on EC2, we always access IMDS with an API token.
|
|
Packit Service |
9bfd13 |
# If we could not get an API token, then we assume the IMDS
|
|
Packit Service |
9bfd13 |
# endpoint was disabled and we move on without a data source.
|
|
Packit Service |
9bfd13 |
# Fallback to IMDSv1 if not running on EC2
|
|
Packit Service |
9bfd13 |
if not metadata_address and self.cloud_name != CloudNames.AWS:
|
|
Packit Service |
a04d08 |
# if we can't get a token, use instance-id path
|
|
Packit Service |
a04d08 |
urls = []
|
|
Packit Service |
a04d08 |
url2base = {}
|
|
Packit Service |
a04d08 |
url_path = '{ver}/meta-data/instance-id'.format(
|
|
Packit Service |
a04d08 |
ver=self.min_metadata_version)
|
|
Packit Service |
a04d08 |
request_method = 'GET'
|
|
Packit Service |
a04d08 |
for url in mdurls:
|
|
Packit Service |
a04d08 |
cur = '{0}/{1}'.format(url, url_path)
|
|
Packit Service |
a04d08 |
urls.append(cur)
|
|
Packit Service |
a04d08 |
url2base[cur] = url
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
start_time = time.time()
|
|
Packit Service |
a04d08 |
url, _ = uhelp.wait_for_url(
|
|
Packit Service |
a04d08 |
urls=urls, max_wait=url_params.max_wait_seconds,
|
|
Packit Service |
a04d08 |
timeout=url_params.timeout_seconds, status_cb=LOG.warning,
|
|
Packit Service |
9bfd13 |
headers_redact=AWS_TOKEN_REDACT, headers_cb=self._get_headers,
|
|
Packit Service |
9bfd13 |
request_method=request_method)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if url:
|
|
Packit Service |
a04d08 |
metadata_address = url2base[url]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if metadata_address:
|
|
Packit Service |
a04d08 |
self.metadata_address = metadata_address
|
|
Packit Service |
a04d08 |
LOG.debug("Using metadata source: '%s'", self.metadata_address)
|
|
Packit Service |
9bfd13 |
elif self.cloud_name == CloudNames.AWS:
|
|
Packit Service |
9bfd13 |
LOG.warning("IMDS's HTTP endpoint is probably disabled")
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
LOG.critical("Giving up on md from %s after %s seconds",
|
|
Packit Service |
a04d08 |
urls, int(time.time() - start_time))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return bool(metadata_address)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def device_name_to_device(self, name):
|
|
Packit Service |
a04d08 |
# Consult metadata service, that has
|
|
Packit Service |
a04d08 |
# ephemeral0: sdb
|
|
Packit Service |
a04d08 |
# and return 'sdb' for input 'ephemeral0'
|
|
Packit Service |
a04d08 |
if 'block-device-mapping' not in self.metadata:
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Example:
|
|
Packit Service |
a04d08 |
# 'block-device-mapping':
|
|
Packit Service |
a04d08 |
# {'ami': '/dev/sda1',
|
|
Packit Service |
a04d08 |
# 'ephemeral0': '/dev/sdb',
|
|
Packit Service |
a04d08 |
# 'root': '/dev/sda1'}
|
|
Packit Service |
a04d08 |
found = None
|
|
Packit Service |
a04d08 |
bdm = self.metadata['block-device-mapping']
|
|
Packit Service |
a04d08 |
if not isinstance(bdm, dict):
|
|
Packit Service |
a04d08 |
LOG.debug("block-device-mapping not a dictionary: '%s'", bdm)
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
for (entname, device) in bdm.items():
|
|
Packit Service |
a04d08 |
if entname == name:
|
|
Packit Service |
a04d08 |
found = device
|
|
Packit Service |
a04d08 |
break
|
|
Packit Service |
a04d08 |
# LP: #513842 mapping in Euca has 'ephemeral' not 'ephemeral0'
|
|
Packit Service |
a04d08 |
if entname == "ephemeral" and name == "ephemeral0":
|
|
Packit Service |
a04d08 |
found = device
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if found is None:
|
|
Packit Service |
a04d08 |
LOG.debug("Unable to convert %s to a device", name)
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
ofound = found
|
|
Packit Service |
a04d08 |
if not found.startswith("/"):
|
|
Packit Service |
a04d08 |
found = "/dev/%s" % found
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if os.path.exists(found):
|
|
Packit Service |
a04d08 |
return found
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
remapped = self._remap_device(os.path.basename(found))
|
|
Packit Service |
a04d08 |
if remapped:
|
|
Packit Service |
a04d08 |
LOG.debug("Remapped device name %s => %s", found, remapped)
|
|
Packit Service |
a04d08 |
return remapped
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# On t1.micro, ephemeral0 will appear in block-device-mapping from
|
|
Packit Service |
a04d08 |
# metadata, but it will not exist on disk (and never will)
|
|
Packit Service |
a04d08 |
# at this point, we've verified that the path did not exist
|
|
Packit Service |
a04d08 |
# in the special case of 'ephemeral0' return None to avoid bogus
|
|
Packit Service |
a04d08 |
# fstab entry (LP: #744019)
|
|
Packit Service |
a04d08 |
if name == "ephemeral0":
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
return ofound
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@property
|
|
Packit Service |
a04d08 |
def availability_zone(self):
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
if self.cloud_name == CloudNames.AWS:
|
|
Packit Service |
a04d08 |
return self.identity.get(
|
|
Packit Service |
a04d08 |
'availabilityZone',
|
|
Packit Service |
a04d08 |
self.metadata['placement']['availability-zone'])
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
return self.metadata['placement']['availability-zone']
|
|
Packit Service |
a04d08 |
except KeyError:
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@property
|
|
Packit Service |
a04d08 |
def region(self):
|
|
Packit Service |
a04d08 |
if self.cloud_name == CloudNames.AWS:
|
|
Packit Service |
a04d08 |
region = self.identity.get('region')
|
|
Packit Service |
a04d08 |
# Fallback to trimming the availability zone if region is missing
|
|
Packit Service |
a04d08 |
if self.availability_zone and not region:
|
|
Packit Service |
a04d08 |
region = self.availability_zone[:-1]
|
|
Packit Service |
a04d08 |
return region
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
az = self.availability_zone
|
|
Packit Service |
a04d08 |
if az is not None:
|
|
Packit Service |
a04d08 |
return az[:-1]
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def activate(self, cfg, is_new_instance):
|
|
Packit Service |
a04d08 |
if not is_new_instance:
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
if self.cloud_name == CloudNames.UNKNOWN:
|
|
Packit Service |
a04d08 |
warn_if_necessary(
|
|
Packit Service |
a04d08 |
util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT),
|
|
Packit Service |
a04d08 |
cfg)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@property
|
|
Packit Service |
a04d08 |
def network_config(self):
|
|
Packit Service |
a04d08 |
"""Return a network config dict for rendering ENI or netplan files."""
|
|
Packit Service |
a04d08 |
if self._network_config != sources.UNSET:
|
|
Packit Service |
a04d08 |
return self._network_config
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if self.metadata is None:
|
|
Packit Service |
a04d08 |
# this would happen if get_data hadn't been called. leave as UNSET
|
|
Packit Service |
a04d08 |
LOG.warning(
|
|
Packit Service |
a04d08 |
"Unexpected call to network_config when metadata is None.")
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
result = None
|
|
Packit Service |
a04d08 |
no_network_metadata_on_aws = bool(
|
|
Packit Service |
a04d08 |
'network' not in self.metadata and
|
|
Packit Service |
a04d08 |
self.cloud_name == CloudNames.AWS)
|
|
Packit Service |
a04d08 |
if no_network_metadata_on_aws:
|
|
Packit Service |
a04d08 |
LOG.debug("Metadata 'network' not present:"
|
|
Packit Service |
a04d08 |
" Refreshing stale metadata from prior to upgrade.")
|
|
Packit Service |
a04d08 |
util.log_time(
|
|
Packit Service |
a04d08 |
logfunc=LOG.debug, msg='Re-crawl of metadata service',
|
|
Packit Service |
a04d08 |
func=self.get_data)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
iface = self.fallback_interface
|
|
Packit Service |
a04d08 |
net_md = self.metadata.get('network')
|
|
Packit Service |
a04d08 |
if isinstance(net_md, dict):
|
|
Packit Service |
9bfd13 |
# SRU_BLOCKER: xenial, bionic and eoan should default
|
|
Packit Service |
9bfd13 |
# apply_full_imds_network_config to False to retain original
|
|
Packit Service |
9bfd13 |
# behavior on those releases.
|
|
Packit Service |
a04d08 |
result = convert_ec2_metadata_network_config(
|
|
Packit Service |
9bfd13 |
net_md, fallback_nic=iface,
|
|
Packit Service |
9bfd13 |
full_network_config=util.get_cfg_option_bool(
|
|
Packit Service |
9bfd13 |
self.ds_cfg, 'apply_full_imds_network_config', True))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# RELEASE_BLOCKER: xenial should drop the below if statement,
|
|
Packit Service |
a04d08 |
# because the issue being addressed doesn't exist pre-netplan.
|
|
Packit Service |
a04d08 |
# (This datasource doesn't implement check_instance_id() so the
|
|
Packit Service |
a04d08 |
# datasource object is recreated every boot; this means we don't
|
|
Packit Service |
a04d08 |
# need to modify update_events on cloud-init upgrade.)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Non-VPC (aka Classic) Ec2 instances need to rewrite the
|
|
Packit Service |
a04d08 |
# network config file every boot due to MAC address change.
|
|
Packit Service |
a04d08 |
if self.is_classic_instance():
|
|
Packit Service |
a04d08 |
self.update_events['network'].add(EventType.BOOT)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
LOG.warning("Metadata 'network' key not valid: %s.", net_md)
|
|
Packit Service |
a04d08 |
self._network_config = result
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return self._network_config
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@property
|
|
Packit Service |
a04d08 |
def fallback_interface(self):
|
|
Packit Service |
a04d08 |
if self._fallback_interface is None:
|
|
Packit Service |
a04d08 |
# fallback_nic was used at one point, so restored objects may
|
|
Packit Service |
a04d08 |
# have an attribute there. respect that if found.
|
|
Packit Service |
a04d08 |
_legacy_fbnic = getattr(self, 'fallback_nic', None)
|
|
Packit Service |
a04d08 |
if _legacy_fbnic:
|
|
Packit Service |
a04d08 |
self._fallback_interface = _legacy_fbnic
|
|
Packit Service |
a04d08 |
self.fallback_nic = None
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
return super(DataSourceEc2, self).fallback_interface
|
|
Packit Service |
a04d08 |
return self._fallback_interface
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def crawl_metadata(self):
|
|
Packit Service |
a04d08 |
"""Crawl metadata service when available.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@returns: Dictionary of crawled metadata content containing the keys:
|
|
Packit Service |
a04d08 |
meta-data, user-data and dynamic.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
if not self.wait_for_metadata_service():
|
|
Packit Service |
a04d08 |
return {}
|
|
Packit Service |
a04d08 |
api_version = self.get_metadata_api_version()
|
|
Packit Service |
9bfd13 |
redact = AWS_TOKEN_REDACT
|
|
Packit Service |
a04d08 |
crawled_metadata = {}
|
|
Packit Service |
a04d08 |
if self.cloud_name == CloudNames.AWS:
|
|
Packit Service |
a04d08 |
exc_cb = self._refresh_stale_aws_token_cb
|
|
Packit Service |
a04d08 |
exc_cb_ud = self._skip_or_refresh_stale_aws_token_cb
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
exc_cb = exc_cb_ud = None
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
crawled_metadata['user-data'] = ec2.get_instance_userdata(
|
|
Packit Service |
a04d08 |
api_version, self.metadata_address,
|
|
Packit Service |
9bfd13 |
headers_cb=self._get_headers, headers_redact=redact,
|
|
Packit Service |
9bfd13 |
exception_cb=exc_cb_ud)
|
|
Packit Service |
a04d08 |
crawled_metadata['meta-data'] = ec2.get_instance_metadata(
|
|
Packit Service |
a04d08 |
api_version, self.metadata_address,
|
|
Packit Service |
9bfd13 |
headers_cb=self._get_headers, headers_redact=redact,
|
|
Packit Service |
9bfd13 |
exception_cb=exc_cb)
|
|
Packit Service |
a04d08 |
if self.cloud_name == CloudNames.AWS:
|
|
Packit Service |
a04d08 |
identity = ec2.get_instance_identity(
|
|
Packit Service |
a04d08 |
api_version, self.metadata_address,
|
|
Packit Service |
9bfd13 |
headers_cb=self._get_headers, headers_redact=redact,
|
|
Packit Service |
9bfd13 |
exception_cb=exc_cb)
|
|
Packit Service |
a04d08 |
crawled_metadata['dynamic'] = {'instance-identity': identity}
|
|
Packit Service |
a04d08 |
except Exception:
|
|
Packit Service |
a04d08 |
util.logexc(
|
|
Packit Service |
a04d08 |
LOG, "Failed reading from metadata address %s",
|
|
Packit Service |
a04d08 |
self.metadata_address)
|
|
Packit Service |
a04d08 |
return {}
|
|
Packit Service |
a04d08 |
crawled_metadata['_metadata_api_version'] = api_version
|
|
Packit Service |
a04d08 |
return crawled_metadata
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _refresh_api_token(self, seconds=AWS_TOKEN_TTL_SECONDS):
|
|
Packit Service |
a04d08 |
"""Request new metadata API token.
|
|
Packit Service |
a04d08 |
@param seconds: The lifetime of the token in seconds
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@return: The API token or None if unavailable.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
if self.cloud_name != CloudNames.AWS:
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
LOG.debug("Refreshing Ec2 metadata API token")
|
|
Packit Service |
9bfd13 |
request_header = {AWS_TOKEN_REQ_HEADER: seconds}
|
|
Packit Service |
a04d08 |
token_url = '{}/{}'.format(self.metadata_address, API_TOKEN_ROUTE)
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
9bfd13 |
response = uhelp.readurl(token_url, headers=request_header,
|
|
Packit Service |
9bfd13 |
headers_redact=AWS_TOKEN_REDACT,
|
|
Packit Service |
9bfd13 |
request_method="PUT")
|
|
Packit Service |
a04d08 |
except uhelp.UrlError as e:
|
|
Packit Service |
a04d08 |
LOG.warning(
|
|
Packit Service |
a04d08 |
'Unable to get API token: %s raised exception %s',
|
|
Packit Service |
a04d08 |
token_url, e)
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
return response.contents
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _skip_or_refresh_stale_aws_token_cb(self, msg, exception):
|
|
Packit Service |
a04d08 |
"""Callback will not retry on SKIP_USERDATA_CODES or if no token
|
|
Packit Service |
a04d08 |
is available."""
|
|
Packit Service |
a04d08 |
retry = ec2.skip_retry_on_codes(
|
|
Packit Service |
a04d08 |
ec2.SKIP_USERDATA_CODES, msg, exception)
|
|
Packit Service |
a04d08 |
if not retry:
|
|
Packit Service |
a04d08 |
return False # False raises exception
|
|
Packit Service |
a04d08 |
return self._refresh_stale_aws_token_cb(msg, exception)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _refresh_stale_aws_token_cb(self, msg, exception):
|
|
Packit Service |
a04d08 |
"""Exception handler for Ec2 to refresh token if token is stale."""
|
|
Packit Service |
a04d08 |
if isinstance(exception, uhelp.UrlError) and exception.code == 401:
|
|
Packit Service |
a04d08 |
# With _api_token as None, _get_headers will _refresh_api_token.
|
|
Packit Service |
a04d08 |
LOG.debug("Clearing cached Ec2 API token due to expiry")
|
|
Packit Service |
a04d08 |
self._api_token = None
|
|
Packit Service |
a04d08 |
return True # always retry
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def _imds_exception_cb(self, msg, exception=None):
|
|
Packit Service |
9bfd13 |
"""Fail quickly on proper AWS if IMDSv2 rejects API token request
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
Guidance from Amazon is that if IMDSv2 had disabled token requests
|
|
Packit Service |
9bfd13 |
by returning a 403, or cloud-init malformed requests resulting in
|
|
Packit Service |
9bfd13 |
other 40X errors, we want the datasource detection to fail quickly
|
|
Packit Service |
9bfd13 |
without retries as those symptoms will likely not be resolved by
|
|
Packit Service |
9bfd13 |
retries.
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
Exceptions such as requests.ConnectionError due to IMDS being
|
|
Packit Service |
9bfd13 |
temporarily unroutable or unavailable will still retry due to the
|
|
Packit Service |
9bfd13 |
callsite wait_for_url.
|
|
Packit Service |
9bfd13 |
"""
|
|
Packit Service |
9bfd13 |
if isinstance(exception, uhelp.UrlError):
|
|
Packit Service |
9bfd13 |
# requests.ConnectionError will have exception.code == None
|
|
Packit Service |
9bfd13 |
if exception.code and exception.code >= 400:
|
|
Packit Service |
9bfd13 |
if exception.code == 403:
|
|
Packit Service |
9bfd13 |
LOG.warning('Ec2 IMDS endpoint returned a 403 error. '
|
|
Packit Service |
9bfd13 |
'HTTP endpoint is disabled. Aborting.')
|
|
Packit Service |
9bfd13 |
else:
|
|
Packit Service |
9bfd13 |
LOG.warning('Fatal error while requesting '
|
|
Packit Service |
9bfd13 |
'Ec2 IMDSv2 API tokens')
|
|
Packit Service |
9bfd13 |
raise exception
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _get_headers(self, url=''):
|
|
Packit Service |
a04d08 |
"""Return a dict of headers for accessing a url.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
If _api_token is unset on AWS, attempt to refresh the token via a PUT
|
|
Packit Service |
a04d08 |
and then return the updated token header.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
9bfd13 |
if self.cloud_name != CloudNames.AWS:
|
|
Packit Service |
a04d08 |
return {}
|
|
Packit Service |
a04d08 |
# Request a 6 hour token if URL is API_TOKEN_ROUTE
|
|
Packit Service |
9bfd13 |
request_token_header = {AWS_TOKEN_REQ_HEADER: AWS_TOKEN_TTL_SECONDS}
|
|
Packit Service |
a04d08 |
if API_TOKEN_ROUTE in url:
|
|
Packit Service |
a04d08 |
return request_token_header
|
|
Packit Service |
a04d08 |
if not self._api_token:
|
|
Packit Service |
a04d08 |
# If we don't yet have an API token, get one via a PUT against
|
|
Packit Service |
a04d08 |
# API_TOKEN_ROUTE. This _api_token may get unset by a 403 due
|
|
Packit Service |
a04d08 |
# to an invalid or expired token
|
|
Packit Service |
a04d08 |
self._api_token = self._refresh_api_token()
|
|
Packit Service |
a04d08 |
if not self._api_token:
|
|
Packit Service |
a04d08 |
return {}
|
|
Packit Service |
9bfd13 |
return {AWS_TOKEN_PUT_HEADER: self._api_token}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class DataSourceEc2Local(DataSourceEc2):
|
|
Packit Service |
a04d08 |
"""Datasource run at init-local which sets up network to query metadata.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
In init-local, no network is available. This subclass sets up minimal
|
|
Packit Service |
a04d08 |
networking with dhclient on a viable nic so that it can talk to the
|
|
Packit Service |
a04d08 |
metadata service. If the metadata service provides network configuration
|
|
Packit Service |
a04d08 |
then render the network configuration for that instance based on metadata.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
perform_dhcp_setup = True # Use dhcp before querying metadata
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_data(self):
|
|
Packit Service |
a04d08 |
supported_platforms = (CloudNames.AWS,)
|
|
Packit Service |
a04d08 |
if self.cloud_name not in supported_platforms:
|
|
Packit Service |
a04d08 |
LOG.debug("Local Ec2 mode only supported on %s, not %s",
|
|
Packit Service |
a04d08 |
supported_platforms, self.cloud_name)
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
return super(DataSourceEc2Local, self).get_data()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def read_strict_mode(cfgval, default):
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
return parse_strict_mode(cfgval)
|
|
Packit Service |
a04d08 |
except ValueError as e:
|
|
Packit Service |
a04d08 |
LOG.warning(e)
|
|
Packit Service |
a04d08 |
return default
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def parse_strict_mode(cfgval):
|
|
Packit Service |
a04d08 |
# given a mode like:
|
|
Packit Service |
a04d08 |
# true, false, warn,[sleep]
|
|
Packit Service |
a04d08 |
# return tuple with string mode (true|false|warn) and sleep.
|
|
Packit Service |
a04d08 |
if cfgval is True:
|
|
Packit Service |
a04d08 |
return 'true', None
|
|
Packit Service |
a04d08 |
if cfgval is False:
|
|
Packit Service |
a04d08 |
return 'false', None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if not cfgval:
|
|
Packit Service |
a04d08 |
return 'warn', 0
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
mode, _, sleep = cfgval.partition(",")
|
|
Packit Service |
a04d08 |
if mode not in ('true', 'false', 'warn'):
|
|
Packit Service |
a04d08 |
raise ValueError(
|
|
Packit Service |
a04d08 |
"Invalid mode '%s' in strict_id setting '%s': "
|
|
Packit Service |
a04d08 |
"Expected one of 'true', 'false', 'warn'." % (mode, cfgval))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if sleep:
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
sleep = int(sleep)
|
|
Packit Service |
9bfd13 |
except ValueError as e:
|
|
Packit Service |
9bfd13 |
raise ValueError(
|
|
Packit Service |
9bfd13 |
"Invalid sleep '%s' in strict_id setting '%s': not an integer"
|
|
Packit Service |
9bfd13 |
% (sleep, cfgval)
|
|
Packit Service |
9bfd13 |
) from e
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
sleep = None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return mode, sleep
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def warn_if_necessary(cfgval, cfg):
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
mode, sleep = parse_strict_mode(cfgval)
|
|
Packit Service |
a04d08 |
except ValueError as e:
|
|
Packit Service |
a04d08 |
LOG.warning(e)
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if mode == "false":
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
warnings.show_warning('non_ec2_md', cfg, mode=True, sleep=sleep)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def identify_aws(data):
|
|
Packit Service |
a04d08 |
# data is a dictionary returned by _collect_platform_data.
|
|
Packit Service |
a04d08 |
if (data['uuid'].startswith('ec2') and
|
|
Packit Service |
a04d08 |
(data['uuid_source'] == 'hypervisor' or
|
|
Packit Service |
a04d08 |
data['uuid'] == data['serial'])):
|
|
Packit Service |
a04d08 |
return CloudNames.AWS
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def identify_brightbox(data):
|
|
Packit Service |
a04d08 |
if data['serial'].endswith('.brightbox.com'):
|
|
Packit Service |
a04d08 |
return CloudNames.BRIGHTBOX
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def identify_zstack(data):
|
|
Packit Service |
a04d08 |
if data['asset_tag'].endswith('.zstack.io'):
|
|
Packit Service |
a04d08 |
return CloudNames.ZSTACK
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def identify_e24cloud(data):
|
|
Packit Service |
a04d08 |
if data['vendor'] == 'e24cloud':
|
|
Packit Service |
a04d08 |
return CloudNames.E24CLOUD
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def identify_platform():
|
|
Packit Service |
a04d08 |
# identify the platform and return an entry in CloudNames.
|
|
Packit Service |
a04d08 |
data = _collect_platform_data()
|
|
Packit Service |
a04d08 |
checks = (identify_aws, identify_brightbox, identify_zstack,
|
|
Packit Service |
a04d08 |
identify_e24cloud, lambda x: CloudNames.UNKNOWN)
|
|
Packit Service |
a04d08 |
for checker in checks:
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
result = checker(data)
|
|
Packit Service |
a04d08 |
if result:
|
|
Packit Service |
a04d08 |
return result
|
|
Packit Service |
a04d08 |
except Exception as e:
|
|
Packit Service |
a04d08 |
LOG.warning("calling %s with %s raised exception: %s",
|
|
Packit Service |
a04d08 |
checker, data, e)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _collect_platform_data():
|
|
Packit Service |
a04d08 |
"""Returns a dictionary of platform info from dmi or /sys/hypervisor.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Keys in the dictionary are as follows:
|
|
Packit Service |
a04d08 |
uuid: system-uuid from dmi or /sys/hypervisor
|
|
Packit Service |
a04d08 |
uuid_source: 'hypervisor' (/sys/hypervisor/uuid) or 'dmi'
|
|
Packit Service |
a04d08 |
serial: dmi 'system-serial-number' (/sys/.../product_serial)
|
|
Packit Service |
a04d08 |
asset_tag: 'dmidecode -s chassis-asset-tag'
|
|
Packit Service |
a04d08 |
vendor: dmi 'system-manufacturer' (/sys/.../sys_vendor)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
On Ec2 instances experimentation is that product_serial is upper case,
|
|
Packit Service |
a04d08 |
and product_uuid is lower case. This returns lower case values for both.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
data = {}
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
uuid = util.load_file("/sys/hypervisor/uuid").strip()
|
|
Packit Service |
a04d08 |
data['uuid_source'] = 'hypervisor'
|
|
Packit Service |
a04d08 |
except Exception:
|
|
Packit Service |
a04d08 |
uuid = util.read_dmi_data('system-uuid')
|
|
Packit Service |
a04d08 |
data['uuid_source'] = 'dmi'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if uuid is None:
|
|
Packit Service |
a04d08 |
uuid = ''
|
|
Packit Service |
a04d08 |
data['uuid'] = uuid.lower()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
serial = util.read_dmi_data('system-serial-number')
|
|
Packit Service |
a04d08 |
if serial is None:
|
|
Packit Service |
a04d08 |
serial = ''
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
data['serial'] = serial.lower()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
asset_tag = util.read_dmi_data('chassis-asset-tag')
|
|
Packit Service |
a04d08 |
if asset_tag is None:
|
|
Packit Service |
a04d08 |
asset_tag = ''
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
data['asset_tag'] = asset_tag.lower()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
vendor = util.read_dmi_data('system-manufacturer')
|
|
Packit Service |
a04d08 |
data['vendor'] = (vendor if vendor else '').lower()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return data
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def convert_ec2_metadata_network_config(
|
|
Packit Service |
9bfd13 |
network_md, macs_to_nics=None, fallback_nic=None,
|
|
Packit Service |
9bfd13 |
full_network_config=True):
|
|
Packit Service |
9bfd13 |
"""Convert ec2 metadata to network config version 2 data dict.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param: network_md: 'network' portion of EC2 metadata.
|
|
Packit Service |
a04d08 |
generally formed as {"interfaces": {"macs": {}} where
|
|
Packit Service |
a04d08 |
'macs' is a dictionary with mac address as key and contents like:
|
|
Packit Service |
a04d08 |
{"device-number": "0", "interface-id": "...", "local-ipv4s": ...}
|
|
Packit Service |
a04d08 |
@param: macs_to_nics: Optional dict of mac addresses and nic names. If
|
|
Packit Service |
a04d08 |
not provided, get_interfaces_by_mac is called to get it from the OS.
|
|
Packit Service |
a04d08 |
@param: fallback_nic: Optionally provide the primary nic interface name.
|
|
Packit Service |
a04d08 |
This nic will be guaranteed to minimally have a dhcp4 configuration.
|
|
Packit Service |
9bfd13 |
@param: full_network_config: Boolean set True to configure all networking
|
|
Packit Service |
9bfd13 |
presented by IMDS. This includes rendering secondary IPv4 and IPv6
|
|
Packit Service |
9bfd13 |
addresses on all NICs and rendering network config on secondary NICs.
|
|
Packit Service |
9bfd13 |
If False, only the primary nic will be configured and only with dhcp
|
|
Packit Service |
9bfd13 |
(IPv4/IPv6).
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
@return A dict of network config version 2 based on the metadata and macs.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
9bfd13 |
netcfg = {'version': 2, 'ethernets': {}}
|
|
Packit Service |
a04d08 |
if not macs_to_nics:
|
|
Packit Service |
a04d08 |
macs_to_nics = net.get_interfaces_by_mac()
|
|
Packit Service |
a04d08 |
macs_metadata = network_md['interfaces']['macs']
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
if not full_network_config:
|
|
Packit Service |
9bfd13 |
for mac, nic_name in macs_to_nics.items():
|
|
Packit Service |
9bfd13 |
if nic_name == fallback_nic:
|
|
Packit Service |
9bfd13 |
break
|
|
Packit Service |
9bfd13 |
dev_config = {'dhcp4': True,
|
|
Packit Service |
9bfd13 |
'dhcp6': False,
|
|
Packit Service |
9bfd13 |
'match': {'macaddress': mac.lower()},
|
|
Packit Service |
9bfd13 |
'set-name': nic_name}
|
|
Packit Service |
9bfd13 |
nic_metadata = macs_metadata.get(mac)
|
|
Packit Service |
9bfd13 |
if nic_metadata.get('ipv6s'): # Any IPv6 addresses configured
|
|
Packit Service |
9bfd13 |
dev_config['dhcp6'] = True
|
|
Packit Service |
9bfd13 |
netcfg['ethernets'][nic_name] = dev_config
|
|
Packit Service |
9bfd13 |
return netcfg
|
|
Packit Service |
9bfd13 |
# Apply network config for all nics and any secondary IPv4/v6 addresses
|
|
Packit Service |
9bfd13 |
for mac, nic_name in sorted(macs_to_nics.items()):
|
|
Packit Service |
a04d08 |
nic_metadata = macs_metadata.get(mac)
|
|
Packit Service |
a04d08 |
if not nic_metadata:
|
|
Packit Service |
a04d08 |
continue # Not a physical nic represented in metadata
|
|
Packit Service |
9bfd13 |
# device-number is zero-indexed, we want it 1-indexed for the
|
|
Packit Service |
9bfd13 |
# multiplication on the following line
|
|
Packit Service |
9bfd13 |
nic_idx = int(nic_metadata['device-number']) + 1
|
|
Packit Service |
9bfd13 |
dhcp_override = {'route-metric': nic_idx * 100}
|
|
Packit Service |
9bfd13 |
dev_config = {'dhcp4': True, 'dhcp4-overrides': dhcp_override,
|
|
Packit Service |
9bfd13 |
'dhcp6': False,
|
|
Packit Service |
9bfd13 |
'match': {'macaddress': mac.lower()},
|
|
Packit Service |
9bfd13 |
'set-name': nic_name}
|
|
Packit Service |
9bfd13 |
if nic_metadata.get('ipv6s'): # Any IPv6 addresses configured
|
|
Packit Service |
9bfd13 |
dev_config['dhcp6'] = True
|
|
Packit Service |
9bfd13 |
dev_config['dhcp6-overrides'] = dhcp_override
|
|
Packit Service |
9bfd13 |
dev_config['addresses'] = get_secondary_addresses(nic_metadata, mac)
|
|
Packit Service |
9bfd13 |
if not dev_config['addresses']:
|
|
Packit Service |
9bfd13 |
dev_config.pop('addresses') # Since we found none configured
|
|
Packit Service |
9bfd13 |
netcfg['ethernets'][nic_name] = dev_config
|
|
Packit Service |
9bfd13 |
# Remove route-metric dhcp overrides if only one nic configured
|
|
Packit Service |
9bfd13 |
if len(netcfg['ethernets']) == 1:
|
|
Packit Service |
9bfd13 |
for nic_name in netcfg['ethernets'].keys():
|
|
Packit Service |
9bfd13 |
netcfg['ethernets'][nic_name].pop('dhcp4-overrides')
|
|
Packit Service |
9bfd13 |
netcfg['ethernets'][nic_name].pop('dhcp6-overrides', None)
|
|
Packit Service |
a04d08 |
return netcfg
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def get_secondary_addresses(nic_metadata, mac):
|
|
Packit Service |
9bfd13 |
"""Parse interface-specific nic metadata and return any secondary IPs
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
:return: List of secondary IPv4 or IPv6 addresses to configure on the
|
|
Packit Service |
9bfd13 |
interface
|
|
Packit Service |
9bfd13 |
"""
|
|
Packit Service |
9bfd13 |
ipv4s = nic_metadata.get('local-ipv4s')
|
|
Packit Service |
9bfd13 |
ipv6s = nic_metadata.get('ipv6s')
|
|
Packit Service |
9bfd13 |
addresses = []
|
|
Packit Service |
9bfd13 |
# In version < 2018-09-24 local_ipv4s or ipv6s is a str with one IP
|
|
Packit Service |
9bfd13 |
if bool(isinstance(ipv4s, list) and len(ipv4s) > 1):
|
|
Packit Service |
9bfd13 |
addresses.extend(
|
|
Packit Service |
9bfd13 |
_get_secondary_addresses(
|
|
Packit Service |
9bfd13 |
nic_metadata, 'subnet-ipv4-cidr-block', mac, ipv4s, '24'))
|
|
Packit Service |
9bfd13 |
if bool(isinstance(ipv6s, list) and len(ipv6s) > 1):
|
|
Packit Service |
9bfd13 |
addresses.extend(
|
|
Packit Service |
9bfd13 |
_get_secondary_addresses(
|
|
Packit Service |
9bfd13 |
nic_metadata, 'subnet-ipv6-cidr-block', mac, ipv6s, '128'))
|
|
Packit Service |
9bfd13 |
return sorted(addresses)
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
def _get_secondary_addresses(nic_metadata, cidr_key, mac, ips, default_prefix):
|
|
Packit Service |
9bfd13 |
"""Return list of IP addresses as CIDRs for secondary IPs
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
The CIDR prefix will be default_prefix if cidr_key is absent or not
|
|
Packit Service |
9bfd13 |
parseable in nic_metadata.
|
|
Packit Service |
9bfd13 |
"""
|
|
Packit Service |
9bfd13 |
addresses = []
|
|
Packit Service |
9bfd13 |
cidr = nic_metadata.get(cidr_key)
|
|
Packit Service |
9bfd13 |
prefix = default_prefix
|
|
Packit Service |
9bfd13 |
if not cidr or len(cidr.split('/')) != 2:
|
|
Packit Service |
9bfd13 |
ip_type = 'ipv4' if 'ipv4' in cidr_key else 'ipv6'
|
|
Packit Service |
9bfd13 |
LOG.warning(
|
|
Packit Service |
9bfd13 |
'Could not parse %s %s for mac %s. %s network'
|
|
Packit Service |
9bfd13 |
' config prefix defaults to /%s',
|
|
Packit Service |
9bfd13 |
cidr_key, cidr, mac, ip_type, prefix)
|
|
Packit Service |
9bfd13 |
else:
|
|
Packit Service |
9bfd13 |
prefix = cidr.split('/')[1]
|
|
Packit Service |
9bfd13 |
# We know we have > 1 ips for in metadata for this IP type
|
|
Packit Service |
9bfd13 |
for ip in ips[1:]:
|
|
Packit Service |
9bfd13 |
addresses.append(
|
|
Packit Service |
9bfd13 |
'{ip}/{prefix}'.format(ip=ip, prefix=prefix))
|
|
Packit Service |
9bfd13 |
return addresses
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
a04d08 |
# Used to match classes to dependencies
|
|
Packit Service |
a04d08 |
datasources = [
|
|
Packit Service |
a04d08 |
(DataSourceEc2Local, (sources.DEP_FILESYSTEM,)), # Run at init-local
|
|
Packit Service |
a04d08 |
(DataSourceEc2, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
|
|
Packit Service |
a04d08 |
]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Return a list of data sources that match this set of dependencies
|
|
Packit Service |
a04d08 |
def get_datasource_list(depends):
|
|
Packit Service |
a04d08 |
return sources.list_from_depends(depends, datasources)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# vi: ts=4 expandtab
|