summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChad Smith <chad.smith@canonical.com>2020-09-15 20:19:10 -0600
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2020-09-16 02:23:11 +0000
commit3ea0982665e4a50c0b75b408fcc900e294ced925 (patch)
tree9376f767ae5f93f6b8a41f4d6b4562eea90a76e2
parentf0ca38837f5d7c5eadf2306493feb183b6f40093 (diff)
downloadcloud-init-git-3ea0982665e4a50c0b75b408fcc900e294ced925.tar.gz
20.3-15-g6d332e5c-0ubuntu1 (patches unapplied)
Imported using git-ubuntu import.
-rw-r--r--cloudinit/config/cc_lxd.py12
-rw-r--r--cloudinit/config/cc_power_state_change.py56
-rwxr-xr-xcloudinit/distros/__init__.py24
-rw-r--r--cloudinit/distros/alpine.py26
-rw-r--r--cloudinit/distros/bsd.py4
-rw-r--r--cloudinit/net/sysconfig.py32
-rwxr-xr-xcloudinit/sources/DataSourceAzure.py53
-rwxr-xr-xcloudinit/sources/helpers/azure.py72
-rw-r--r--cloudinit/user_data.py1
-rw-r--r--debian/changelog37
-rw-r--r--debian/clean2
-rw-r--r--debian/cloud-init.postinst44
-rw-r--r--debian/cloud-init.templates2
-rw-r--r--debian/compat1
-rw-r--r--debian/control4
-rwxr-xr-xdebian/rules6
-rw-r--r--doc/rtd/topics/datasources/azure.rst6
-rw-r--r--doc/rtd/topics/network-config-format-v1.rst2
-rw-r--r--integration-requirements.txt10
-rw-r--r--packages/redhat/cloud-init.spec.in9
-rw-r--r--systemd/cloud-init-local.service.tmpl1
-rw-r--r--systemd/cloud-init.service.tmpl2
-rw-r--r--tests/unittests/test_datasource/test_azure.py64
-rw-r--r--tests/unittests/test_datasource/test_azure_helper.py13
-rw-r--r--tests/unittests/test_distros/test_netconfig.py81
-rw-r--r--tests/unittests/test_handler/test_handler_lxd.py2
-rw-r--r--tests/unittests/test_handler/test_handler_power_state.py58
-rw-r--r--tests/unittests/test_net.py4
-rw-r--r--tools/.github-cla-signers2
29 files changed, 498 insertions, 132 deletions
diff --git a/cloudinit/config/cc_lxd.py b/cloudinit/config/cc_lxd.py
index 7129c9c6..486037d9 100644
--- a/cloudinit/config/cc_lxd.py
+++ b/cloudinit/config/cc_lxd.py
@@ -283,14 +283,18 @@ def maybe_cleanup_default(net_name, did_init, create, attach,
fail_assume_enoent = "failed. Assuming it did not exist."
succeeded = "succeeded."
if create:
- msg = "Deletion of lxd network '%s' %s"
+ msg = "Detach of lxd network '%s' from profile '%s' %s"
try:
- _lxc(["network", "delete", net_name])
- LOG.debug(msg, net_name, succeeded)
+ _lxc(["network", "detach-profile", net_name, profile])
+ LOG.debug(msg, net_name, profile, succeeded)
except subp.ProcessExecutionError as e:
if e.exit_code != 1:
raise e
- LOG.debug(msg, net_name, fail_assume_enoent)
+ LOG.debug(msg, net_name, profile, fail_assume_enoent)
+ else:
+ msg = "Deletion of lxd network '%s' %s"
+ _lxc(["network", "delete", net_name])
+ LOG.debug(msg, net_name, succeeded)
if attach:
msg = "Removal of device '%s' from profile '%s' %s"
diff --git a/cloudinit/config/cc_power_state_change.py b/cloudinit/config/cc_power_state_change.py
index 6fcb8a7d..b0cfafcd 100644
--- a/cloudinit/config/cc_power_state_change.py
+++ b/cloudinit/config/cc_power_state_change.py
@@ -117,7 +117,7 @@ def check_condition(cond, log=None):
def handle(_name, cfg, cloud, log, _args):
try:
- (args, timeout, condition) = load_power_state(cfg, cloud.distro.name)
+ (args, timeout, condition) = load_power_state(cfg, cloud.distro)
if args is None:
log.debug("no power_state provided. doing nothing")
return
@@ -144,19 +144,7 @@ def handle(_name, cfg, cloud, log, _args):
condition, execmd, [args, devnull_fp])
-def convert_delay(delay, fmt=None, scale=None):
- if not fmt:
- fmt = "+%s"
- if not scale:
- scale = 1
-
- if delay != "now":
- delay = fmt % int(int(delay) * int(scale))
-
- return delay
-
-
-def load_power_state(cfg, distro_name):
+def load_power_state(cfg, distro):
# returns a tuple of shutdown_command, timeout
# shutdown_command is None if no config found
pstate = cfg.get('power_state')
@@ -167,44 +155,16 @@ def load_power_state(cfg, distro_name):
if not isinstance(pstate, dict):
raise TypeError("power_state is not a dict.")
- opt_map = {'halt': '-H', 'poweroff': '-P', 'reboot': '-r'}
-
+ modes_ok = ['halt', 'poweroff', 'reboot']
mode = pstate.get("mode")
- if mode not in opt_map:
+ if mode not in distro.shutdown_options_map:
raise TypeError(
"power_state[mode] required, must be one of: %s. found: '%s'." %
- (','.join(opt_map.keys()), mode))
-
- delay = pstate.get("delay", "now")
- message = pstate.get("message")
- scale = 1
- fmt = "+%s"
- command = ["shutdown", opt_map[mode]]
-
- if distro_name == 'alpine':
- # Convert integer 30 or string '30' to '1800' (seconds) as Alpine's
- # halt/poweroff/reboot commands take seconds rather than minutes.
- scale = 60
- # No "+" in front of delay value as not supported by Alpine's commands.
- fmt = "%s"
- if delay == "now":
- # Alpine's commands do not understand "now".
- delay = "0"
- command = [mode, "-d"]
- # Alpine's commands don't support a message.
- message = None
-
- try:
- delay = convert_delay(delay, fmt=fmt, scale=scale)
- except ValueError as e:
- raise TypeError(
- "power_state[delay] must be 'now' or '+m' (minutes)."
- " found '%s'." % delay
- ) from e
+ (','.join(modes_ok), mode))
- args = command + [delay]
- if message:
- args.append(message)
+ args = distro.shutdown_command(mode=mode,
+ delay=pstate.get("delay", "now"),
+ message=pstate.get("message"))
try:
timeout = float(pstate.get('timeout', 30.0))
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 2537608f..fac8cf67 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -73,6 +73,9 @@ class Distro(metaclass=abc.ABCMeta):
renderer_configs = {}
_preferred_ntp_clients = None
networking_cls = LinuxNetworking
+ # This is used by self.shutdown_command(), and can be overridden in
+ # subclasses
+ shutdown_options_map = {'halt': '-H', 'poweroff': '-P', 'reboot': '-r'}
def __init__(self, name, cfg, paths):
self._paths = paths
@@ -250,8 +253,9 @@ class Distro(metaclass=abc.ABCMeta):
distros = []
for family in family_list:
if family not in OSFAMILIES:
- raise ValueError("No distibutions found for osfamily %s"
- % (family))
+ raise ValueError(
+ "No distributions found for osfamily {}".format(family)
+ )
distros.extend(OSFAMILIES[family])
return distros
@@ -749,6 +753,22 @@ class Distro(metaclass=abc.ABCMeta):
subp.subp(['usermod', '-a', '-G', name, member])
LOG.info("Added user '%s' to group '%s'", member, name)
+ def shutdown_command(self, *, mode, delay, message):
+ # called from cc_power_state_change.load_power_state
+ command = ["shutdown", self.shutdown_options_map[mode]]
+ try:
+ if delay != "now":
+ delay = "+%d" % int(delay)
+ except ValueError as e:
+ raise TypeError(
+ "power_state[delay] must be 'now' or '+m' (minutes)."
+ " found '%s'." % (delay,)
+ ) from e
+ args = command + [delay]
+ if message:
+ args.append(message)
+ return args
+
def _apply_hostname_transformations_to_url(url: str, transformations: list):
"""
diff --git a/cloudinit/distros/alpine.py b/cloudinit/distros/alpine.py
index e42443fc..e92ff3fb 100644
--- a/cloudinit/distros/alpine.py
+++ b/cloudinit/distros/alpine.py
@@ -162,4 +162,30 @@ class Distro(distros.Distro):
return self._preferred_ntp_clients
+ def shutdown_command(self, mode='poweroff', delay='now', message=None):
+ # called from cc_power_state_change.load_power_state
+ # Alpine has halt/poweroff/reboot, with the following specifics:
+ # - we use them rather than the generic "shutdown"
+ # - delay is given with "-d [integer]"
+ # - the integer is in seconds, cannot be "now", and takes no "+"
+ # - no message is supported (argument ignored, here)
+
+ command = [mode, "-d"]
+
+ # Convert delay from minutes to seconds, as Alpine's
+ # halt/poweroff/reboot commands take seconds rather than minutes.
+ if delay == "now":
+ # Alpine's commands do not understand "now".
+ command += ['0']
+ else:
+ try:
+ command.append(str(int(delay) * 60))
+ except ValueError as e:
+ raise TypeError(
+ "power_state[delay] must be 'now' or '+m' (minutes)."
+ " found '%s'." % (delay,)
+ ) from e
+
+ return command
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/bsd.py b/cloudinit/distros/bsd.py
index 2ed7a7d5..f717a667 100644
--- a/cloudinit/distros/bsd.py
+++ b/cloudinit/distros/bsd.py
@@ -17,6 +17,10 @@ class BSD(distros.Distro):
hostname_conf_fn = '/etc/rc.conf'
rc_conf_fn = "/etc/rc.conf"
+ # This differs from the parent Distro class, which has -P for
+ # poweroff.
+ shutdown_options_map = {'halt': '-H', 'poweroff': '-p', 'reboot': '-r'}
+
# Set in BSD distro subclasses
group_add_cmd_prefix = []
pkg_cmd_install_prefix = []
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 0a5d481d..e9337b12 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -99,6 +99,10 @@ class ConfigMap(object):
def __len__(self):
return len(self._conf)
+ def skip_key_value(self, key, val):
+ """Skip the pair key, value if it matches a certain rule."""
+ return False
+
def to_string(self):
buf = io.StringIO()
buf.write(_make_header())
@@ -106,6 +110,8 @@ class ConfigMap(object):
buf.write("\n")
for key in sorted(self._conf.keys()):
value = self._conf[key]
+ if self.skip_key_value(key, value):
+ continue
if isinstance(value, bool):
value = self._bool_map[value]
if not isinstance(value, str):
@@ -214,6 +220,7 @@ class NetInterface(ConfigMap):
'bond': 'Bond',
'bridge': 'Bridge',
'infiniband': 'InfiniBand',
+ 'vlan': 'Vlan',
}
def __init__(self, iface_name, base_sysconf_dir, templates,
@@ -267,6 +274,11 @@ class NetInterface(ConfigMap):
c.routes = self.routes.copy()
return c
+ def skip_key_value(self, key, val):
+ if key == 'TYPE' and val == 'Vlan':
+ return True
+ return False
+
class Renderer(renderer.Renderer):
"""Renders network information in a /etc/sysconfig format."""
@@ -697,7 +709,16 @@ class Renderer(renderer.Renderer):
iface_cfg['ETHERDEVICE'] = iface_name[:iface_name.rfind('.')]
else:
iface_cfg['VLAN'] = True
- iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')]
+ iface_cfg.kind = 'vlan'
+
+ rdev = iface['vlan-raw-device']
+ supported = _supported_vlan_names(rdev, iface['vlan_id'])
+ if iface_name not in supported:
+ LOG.info(
+ "Name '%s' for vlan '%s' is not officially supported"
+ "by RHEL. Supported: %s",
+ iface_name, rdev, ' '.join(supported))
+ iface_cfg['PHYSDEV'] = rdev
iface_subnets = iface.get("subnets", [])
route_cfg = iface_cfg.routes
@@ -896,6 +917,15 @@ class Renderer(renderer.Renderer):
"\n".join(netcfg) + "\n", file_mode)
+def _supported_vlan_names(rdev, vid):
+ """Return list of supported names for vlan devices per RHEL doc
+ 11.5. Naming Scheme for VLAN Interfaces."""
+ return [
+ v.format(rdev=rdev, vid=int(vid))
+ for v in ("{rdev}{vid:04}", "{rdev}{vid}",
+ "{rdev}.{vid:04}", "{rdev}.{vid}")]
+
+
def available(target=None):
sysconfig = available_sysconfig(target=target)
nm = available_nm(target=target)
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index f3c6452b..e98fd497 100755
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -561,6 +561,40 @@ class DataSourceAzure(sources.DataSource):
def device_name_to_device(self, name):
return self.ds_cfg['disk_aliases'].get(name)
+ @azure_ds_telemetry_reporter
+ def get_public_ssh_keys(self):
+ """
+ Try to get the ssh keys from IMDS first, and if that fails
+ (i.e. IMDS is unavailable) then fallback to getting the ssh
+ keys from OVF.
+
+ The benefit to getting keys from IMDS is a large performance
+ advantage, so this is a strong preference. But we must keep
+ OVF as a second option for environments that don't have IMDS.
+ """
+ LOG.debug('Retrieving public SSH keys')
+ ssh_keys = []
+ try:
+ ssh_keys = [
+ public_key['keyData']
+ for public_key
+ in self.metadata['imds']['compute']['publicKeys']
+ ]
+ LOG.debug('Retrieved SSH keys from IMDS')
+ except KeyError:
+ log_msg = 'Unable to get keys from IMDS, falling back to OVF'
+ LOG.debug(log_msg)
+ report_diagnostic_event(log_msg)
+ try:
+ ssh_keys = self.metadata['public-keys']
+ LOG.debug('Retrieved keys from OVF')
+ except KeyError:
+ log_msg = 'No keys available from OVF'
+ LOG.debug(log_msg)
+ report_diagnostic_event(log_msg)
+
+ return ssh_keys
+
def get_config_obj(self):
return self.cfg
@@ -764,7 +798,22 @@ class DataSourceAzure(sources.DataSource):
if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN:
self.bounce_network_with_azure_hostname()
- pubkey_info = self.cfg.get('_pubkeys', None)
+ pubkey_info = None
+ try:
+ public_keys = self.metadata['imds']['compute']['publicKeys']
+ LOG.debug(
+ 'Successfully retrieved %s key(s) from IMDS',
+ len(public_keys)
+ if public_keys is not None
+ else 0
+ )
+ except KeyError:
+ LOG.debug(
+ 'Unable to retrieve SSH keys from IMDS during '
+ 'negotiation, falling back to OVF'
+ )
+ pubkey_info = self.cfg.get('_pubkeys', None)
+
metadata_func = partial(get_metadata_from_fabric,
fallback_lease_file=self.
dhclient_lease_file,
@@ -1443,7 +1492,7 @@ def get_metadata_from_imds(fallback_nic, retries):
@azure_ds_telemetry_reporter
def _get_metadata_from_imds(retries):
- url = IMDS_URL + "instance?api-version=2017-12-01"
+ url = IMDS_URL + "instance?api-version=2019-06-01"
headers = {"Metadata": "true"}
try:
response = readurl(
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index b968a96f..79445a81 100755
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -288,11 +288,16 @@ class InvalidGoalStateXMLException(Exception):
class GoalState:
- def __init__(self, unparsed_xml, azure_endpoint_client):
+ def __init__(
+ self,
+ unparsed_xml: str,
+ azure_endpoint_client: AzureEndpointHttpClient,
+ need_certificate: bool = True) -> None:
"""Parses a GoalState XML string and returns a GoalState object.
@param unparsed_xml: string representing a GoalState XML.
- @param azure_endpoint_client: instance of AzureEndpointHttpClient
+ @param azure_endpoint_client: instance of AzureEndpointHttpClient.
+ @param need_certificate: switch to know if certificates is needed.
@return: GoalState object representing the GoalState XML string.
"""
self.azure_endpoint_client = azure_endpoint_client
@@ -321,7 +326,7 @@ class GoalState:
url = self._text_from_xpath(
'./Container/RoleInstanceList/RoleInstance'
'/Configuration/Certificates')
- if url is not None:
+ if url is not None and need_certificate:
with events.ReportEventStack(
name="get-certificates-xml",
description="get certificates xml",
@@ -478,7 +483,10 @@ class GoalStateHealthReporter:
PROVISIONING_SUCCESS_STATUS = 'Ready'
- def __init__(self, goal_state, azure_endpoint_client, endpoint):
+ def __init__(
+ self, goal_state: GoalState,
+ azure_endpoint_client: AzureEndpointHttpClient,
+ endpoint: str) -> None:
"""Creates instance that will report provisioning status to an endpoint
@param goal_state: An instance of class GoalState that contains
@@ -495,7 +503,7 @@ class GoalStateHealthReporter:
self._endpoint = endpoint
@azure_ds_telemetry_reporter
- def send_ready_signal(self):
+ def send_ready_signal(self) -> None:
document = self.build_report(
incarnation=self._goal_state.incarnation,
container_id=self._goal_state.container_id,
@@ -513,8 +521,8 @@ class GoalStateHealthReporter:
LOG.info('Reported ready to Azure fabric.')
def build_report(
- self, incarnation, container_id, instance_id,
- status, substatus=None, description=None):
+ self, incarnation: str, container_id: str, instance_id: str,
+ status: str, substatus=None, description=None) -> str:
health_detail = ''
if substatus is not None:
health_detail = self.HEALTH_DETAIL_SUBSECTION_XML_TEMPLATE.format(
@@ -530,7 +538,7 @@ class GoalStateHealthReporter:
return health_report
@azure_ds_telemetry_reporter
- def _post_health_report(self, document):
+ def _post_health_report(self, document: str) -> None:
push_log_to_kvp()
# Whenever report_diagnostic_event(diagnostic_msg) is invoked in code,
@@ -726,7 +734,7 @@ class WALinuxAgentShim:
return endpoint_ip_address
@azure_ds_telemetry_reporter
- def register_with_azure_and_fetch_data(self, pubkey_info=None):
+ def register_with_azure_and_fetch_data(self, pubkey_info=None) -> dict:
"""Gets the VM's GoalState from Azure, uses the GoalState information
to report ready/send the ready signal/provisioning complete signal to
Azure, and then uses pubkey_info to filter and obtain the user's
@@ -737,30 +745,41 @@ class WALinuxAgentShim:
GoalState.
@return: The list of user's authorized pubkey values.
"""
- if self.openssl_manager is None:
+ http_client_certificate = None
+ if self.openssl_manager is None and pubkey_info is not None:
self.openssl_manager = OpenSSLManager()
+ http_client_certificate = self.openssl_manager.certificate
if self.azure_endpoint_client is None:
self.azure_endpoint_client = AzureEndpointHttpClient(
- self.openssl_manager.certificate)
- goal_state = self._fetch_goal_state_from_azure()
- ssh_keys = self._get_user_pubkeys(goal_state, pubkey_info)
+ http_client_certificate)
+ goal_state = self._fetch_goal_state_from_azure(
+ need_certificate=http_client_certificate is not None
+ )
+ ssh_keys = None
+ if pubkey_info is not None:
+ ssh_keys = self._get_user_pubkeys(goal_state, pubkey_info)
health_reporter = GoalStateHealthReporter(
goal_state, self.azure_endpoint_client, self.endpoint)
health_reporter.send_ready_signal()
return {'public-keys': ssh_keys}
@azure_ds_telemetry_reporter
- def _fetch_goal_state_from_azure(self):
+ def _fetch_goal_state_from_azure(
+ self,
+ need_certificate: bool) -> GoalState:
"""Fetches the GoalState XML from the Azure endpoint, parses the XML,
and returns a GoalState object.
@return: GoalState object representing the GoalState XML
"""
unparsed_goal_state_xml = self._get_raw_goal_state_xml_from_azure()
- return self._parse_raw_goal_state_xml(unparsed_goal_state_xml)
+ return self._parse_raw_goal_state_xml(
+ unparsed_goal_state_xml,
+ need_certificate
+ )
@azure_ds_telemetry_reporter
- def _get_raw_goal_state_xml_from_azure(self):
+ def _get_raw_goal_state_xml_from_azure(self) -> str:
"""Fetches the GoalState XML from the Azure endpoint and returns
the XML as a string.
@@ -770,7 +789,11 @@ class WALinuxAgentShim:
LOG.info('Registering with Azure...')
url = 'http://{}/machine/?comp=goalstate'.format(self.endpoint)
try:
- response = self.azure_endpoint_client.get(url)
+ with events.ReportEventStack(
+ name="goalstate-retrieval",
+ description="retrieve goalstate",
+ parent=azure_ds_reporter):
+ response = self.azure_endpoint_client.get(url)
except Exception as e:
msg = 'failed to register with Azure: %s' % e
LOG.warning(msg)
@@ -780,7 +803,10 @@ class WALinuxAgentShim:
return response.contents
@azure_ds_telemetry_reporter
- def _parse_raw_goal_state_xml(self, unparsed_goal_state_xml):
+ def _parse_raw_goal_state_xml(
+ self,
+ unparsed_goal_state_xml: str,
+ need_certificate: bool) -> GoalState:
"""Parses a GoalState XML string and returns a GoalState object.
@param unparsed_goal_state_xml: GoalState XML string
@@ -788,7 +814,10 @@ class WALinuxAgentShim:
"""
try:
goal_state = GoalState(
- unparsed_goal_state_xml, self.azure_endpoint_client)
+ unparsed_goal_state_xml,
+ self.azure_endpoint_client,
+ need_certificate
+ )
except Exception as e:
msg = 'Error processing GoalState XML: %s' % e
LOG.warning(msg)
@@ -803,7 +832,8 @@ class WALinuxAgentShim:
return goal_state
@azure_ds_telemetry_reporter
- def _get_user_pubkeys(self, goal_state, pubkey_info):
+ def _get_user_pubkeys(
+ self, goal_state: GoalState, pubkey_info: list) -> list:
"""Gets and filters the VM admin user's authorized pubkeys.
The admin user in this case is the username specified as "admin"
@@ -838,7 +868,7 @@ class WALinuxAgentShim:
return ssh_keys
@staticmethod
- def _filter_pubkeys(keys_by_fingerprint, pubkey_info):
+ def _filter_pubkeys(keys_by_fingerprint: dict, pubkey_info: list) -> list:
""" Filter and return only the user's actual pubkeys.
@param keys_by_fingerprint: pubkey fingerprint -> pubkey value dict
diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py
index f234b962..1317e063 100644
--- a/cloudinit/user_data.py
+++ b/cloudinit/user_data.py
@@ -26,7 +26,6 @@ LOG = logging.getLogger(__name__)
NOT_MULTIPART_TYPE = handlers.NOT_MULTIPART_TYPE
PART_FN_TPL = handlers.PART_FN_TPL
OCTET_TYPE = handlers.OCTET_TYPE
-INCLUDE_MAP = handlers.INCLUSION_TYPES_MAP
# Saves typing errors
CONTENT_TYPE = 'Content-Type'
diff --git a/debian/changelog b/debian/changelog
index ead410b1..52afc375 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,40 @@
+cloud-init (20.3-15-g6d332e5c-0ubuntu1) groovy; urgency=medium
+
+ * d/cloud-init.postinst: fix the grub install device for NVMe-rooted
+ instances on upgrade. (LP: #1889555)
+ * d/cloud-init.templates: add RbxCloud to Choices-C.
+ * Add d/clean to fully clean the build artifacts.
+ * d/control:
+ - Bump Standards-Version to 4.5.0, no changes needed.
+ - B-D on debhelper-compat; drop d/compat.
+ * Bump the debhelper compat level to 13. Required changes:
+ - Stop including the dh systemd plugin.
+ - Switch from dh_systemd_start to dh_installsystemd
+ * New upstream snapshot.
+ - create a shutdown_command method in distro classes (#567)
+ [Emmanuel Thomé]
+ - user_data: remove unused constant (#566)
+ - network: Fix type and respect name when rendering vlan in
+ sysconfig. (#541) [Eduardo Otubo] (LP: #1788915, #1826608)
+ - Retrieve SSH keys from IMDS first with OVF as a fallback (#509)
+ [Thomas Stringer]
+ - Add jqueuniet as contributor (#569) [Johann Queuniet]
+ - distros: minor typo fix (#562)
+ - Bump the integration-requirements versioned dependencies (#565)
+ [Paride Legovini]
+ - network-config-format-v1: fix typo in nameserver example (#564)
+ [Stanislas]
+ - Run cloud-init-local.service after the hv_kvp_daemon (#505)
+ [Robert Schweikert]
+ - Add method type hints for Azure helper (#540) [Johnson Shi]
+ - systemd: add Before=shutdown.target when Conflicts=shutdown.target is
+ used (#546) [Paride Legovini]
+ - LXD: detach network from profile before deleting it (#542)
+ [Paride Legovini] (LP: #1776958)
+ - redhat spec: add missing BuildRequires (#552) [Paride Legovini]
+
+ -- Chad Smith <chad.smith@canonical.com> Tue, 15 Sep 2020 20:19:10 -0600
+
cloud-init (20.3-2-g371b392c-0ubuntu1) groovy; urgency=medium
* New upstream snapshot.
diff --git a/debian/clean b/debian/clean
new file mode 100644
index 00000000..de33a5f4
--- /dev/null
+++ b/debian/clean
@@ -0,0 +1,2 @@
+.pytest_cache/
+cloud_init.egg-info/
diff --git a/debian/cloud-init.postinst b/debian/cloud-init.postinst
index acd45fd1..bb1535e8 100644
--- a/debian/cloud-init.postinst
+++ b/debian/cloud-init.postinst
@@ -285,6 +285,49 @@ cleanup_ureadahead() {
/etc/init/ureadahead.conf.disabled /etc/init/ureadahead.conf
}
+fix_lp1889555() {
+ local oldver="$1" last_bad_ver="20.3-2-g371b392c-0ubuntu1"
+ dpkg --compare-versions "$oldver" le-nl "$last_bad_ver" || return 0
+
+ # if cloud-init's grub module did not run, then it did not break anything.
+ [ -f /var/lib/cloud/instance/sem/config_grub_dpkg ] || return 0
+
+ # Don't do anything unless we have grub
+ [ -x /usr/sbin/grub-install ] || return 0
+
+ # Make sure that we are not chrooted.
+ [ "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" ] && return 0
+
+ # Check if we are in a container, i.e. LXC
+ if systemd-detect-virt --quiet --container || lxc-is-container 2>/dev/null; then
+ return 0
+ fi
+
+ # This bug only applies to NVMe devices
+ [ -e /dev/nvme0 ] || return 0
+
+ db_get grub-pc/install_devices && grub_cfg_dev=${RET} || return 0
+
+ # If the current setting is not the (potentially-incorrect) default we
+ # expect, this implies user intervention so leave things alone
+ [ "$grub_cfg_dev" = "/dev/sda" ] || return 0
+
+ correct_idev="$(python3 -c "import logging; from cloudinit.config.cc_grub_dpkg import fetch_idevs; print(fetch_idevs(logging.getLogger()))")" || return 0
+
+ # If correct_idev is the empty string, we failed to determine the correct
+ # install device; do nothing
+ [ -z "$correct_idev" ] && return 0
+
+ # If the correct_idev is already configured, do nothing
+ [ "$grub_cfg_dev" = "$correct_idev" ] && return 0
+
+ echo "Reconfiguring grub install device due to mismatch (LP: #1889555)"
+ echo " grub should use $correct_idev but is configured for $grub_cfg_dev"
+ db_set grub-pc/install_devices "$correct_idev"
+ db_set grub-pc/install_devices_empty "false"
+}
+
+
if [ "$1" = "configure" ]; then
if db_get cloud-init/datasources; then
values="$RET"
@@ -314,6 +357,7 @@ EOF
fix_azure_upgrade_1611074 "$2"
cleanup_ureadahead "$2"
+ fix_lp1889555 "$2"
fi
#DEBHELPER#
diff --git a/debian/cloud-init.templates b/debian/cloud-init.templates
index e55f7726..bd72d31c 100644
--- a/debian/cloud-init.templates
+++ b/debian/cloud-init.templates
@@ -1,7 +1,7 @@
Template: cloud-init/datasources
Type: multiselect
Default: NoCloud, ConfigDrive, OpenNebula, DigitalOcean, Azure, AltCloud, OVF, MAAS, GCE, OpenStack, CloudSigma, SmartOS, Bigstep, Scaleway, AliYun, Ec2, CloudStack, Hetzner, IBMCloud, Oracle, Exoscale, RbxCloud, None
-Choices-C: NoCloud, ConfigDrive, OpenNebula, DigitalOcean, Azure, AltCloud, OVF, MAAS, GCE, OpenStack, CloudSigma, SmartOS, Bigstep, Scaleway, AliYun, Ec2, CloudStack, Hetzner, IBMCloud, Oracle, Exoscale, None
+Choices-C: NoCloud, ConfigDrive, OpenNebula, DigitalOcean, Azure, AltCloud, OVF, MAAS, GCE, OpenStack, CloudSigma, SmartOS, Bigstep, Scaleway, AliYun, Ec2, CloudStack, Hetzner, IBMCloud, Oracle, Exoscale, RbxCloud, None
__Choices: NoCloud: Reads info from /var/lib/cloud/seed only, ConfigDrive: Reads data from Openstack Config Drive, OpenNebula: read from OpenNebula context disk, DigitalOcean: reads data from Droplet datasource, Azure: read from MS Azure cdrom. Requires walinux-agent, AltCloud: config disks for RHEVm and vSphere, OVF: Reads data from OVF Transports, MAAS: Reads data from Ubuntu MAAS, GCE: google compute metadata service, OpenStack: native openstack metadata service, CloudSigma: metadata over serial for cloudsigma.com, SmartOS: Read from SmartOS metadata service, Bigstep: Bigstep metadata service, Scaleway: Scaleway metadata service, AliYun: Alibaba metadata service, Ec2: reads data from EC2 Metadata service, CloudStack: Read from CloudStack metadata service, Hetzner: Hetzner Cloud, IBMCloud: IBM Cloud. Previously softlayer or bluemix., Oracle: Oracle Compute Infrastructure, Exoscale: Exoscale, RbxCloud: HyperOne and Rootbox platforms, None: Failsafe datasource
_Description: Which data sources should be searched?
Cloud-init supports searching different "Data Sources" for information
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec635144..00000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
index 2f5bd90b..90196942 100644
--- a/debian/control
+++ b/debian/control
@@ -3,7 +3,7 @@ Section: admin
Priority: optional
Homepage: https://cloud-init.io/
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
-Build-Depends: debhelper (>= 9.20160709),
+Build-Depends: debhelper-compat (= 13),
dh-python,
iproute2,
pep8,
@@ -24,7 +24,7 @@ Build-Depends: debhelper (>= 9.20160709),
XS-Python-Version: all
Vcs-Browser: https://github.com/canonical/cloud-init/tree/ubuntu/devel
Vcs-Git: https://github.com/canonical/cloud-init -b ubuntu/devel
-Standards-Version: 4.4.1
+Standards-Version: 4.5.0
Rules-Requires-Root: no
Package: cloud-init
diff --git a/debian/rules b/debian/rules
index 90f98bb5..108a9442 100755
--- a/debian/rules
+++ b/debian/rules
@@ -6,7 +6,7 @@ INIT_SYSTEM ?= systemd
export PYBUILD_INSTALL_ARGS=--init-system=$(INIT_SYSTEM)
%:
- dh $@ --with python3,systemd --buildsystem pybuild
+ dh $@ --with python3 --buildsystem pybuild
override_dh_auto_test:
ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS)))
@@ -15,8 +15,8 @@ else
@echo check disabled by DEB_BUILD_OPTIONS=$(DEB_BUILD_OPTIONS)
endif
-override_dh_systemd_start:
- dh_systemd_start --no-restart-on-upgrade --no-start
+override_dh_installsystemd:
+ dh_installsystemd --no-restart-on-upgrade --no-start
override_dh_auto_install:
dh_auto_install --destdir=debian/cloud-init
diff --git a/doc/rtd/topics/datasources/azure.rst b/doc/rtd/topics/datasources/azure.rst
index fdb919a5..e04c3a33 100644
--- a/doc/rtd/topics/datasources/azure.rst
+++ b/doc/rtd/topics/datasources/azure.rst
@@ -68,6 +68,12 @@ configuration information to the instance. Cloud-init uses the IMDS for:
- network configuration for the instance which is applied per boot
- a preprovisioing gate which blocks instance configuration until Azure fabric
is ready to provision
+- retrieving SSH public keys. Cloud-init will first try to utilize SSH keys
+ returned from IMDS, and if they are not provided from IMDS then it will
+ fallback to using the OVF file provided from the CD-ROM. There is a large
+ performance benefit to using IMDS for SSH key retrieval, but in order to
+ support environments where IMDS is not available then we must continue to
+ all for keys from OVF
Configuration
diff --git a/doc/rtd/topics/network-config-format-v1.rst b/doc/rtd/topics/network-config-format-v1.rst
index 9723d689..dfbde514 100644
--- a/doc/rtd/topics/network-config-format-v1.rst
+++ b/doc/rtd/topics/network-config-format-v1.rst
@@ -332,7 +332,7 @@ the following keys:
- type: static
address: 192.168.23.14/27
gateway: 192.168.23.1
- - type: nameserver:
+ - type: nameserver
address:
- 192.168.23.2
- 8.8.8.8
diff --git a/integration-requirements.txt b/integration-requirements.txt
index 44e45c1b..13cfb9d7 100644
--- a/integration-requirements.txt
+++ b/integration-requirements.txt
@@ -6,16 +6,14 @@
#
# ec2 backend
-boto3==1.5.9
+boto3==1.14.53
# ssh communication
-paramiko==2.4.2
-cryptography==2.4.2
-
+paramiko==2.7.2
+cryptography==3.1
# lxd backend
-# 04/03/2018: enables use of lxd 3.0
-git+https://github.com/lxc/pylxd.git@4b8ab1802f9aee4eb29cf7b119dae0aa47150779
+pylxd==2.2.11
# finds latest image information
git+https://git.launchpad.net/simplestreams
diff --git a/packages/redhat/cloud-init.spec.in b/packages/redhat/cloud-init.spec.in
index 4cff2c97..16138012 100644
--- a/packages/redhat/cloud-init.spec.in
+++ b/packages/redhat/cloud-init.spec.in
@@ -39,10 +39,7 @@ Requires(post): chkconfig
Requires(preun): chkconfig
%endif
-# These are runtime dependencies, but declared as BuildRequires so that
-# - tests can be run here.
-# - parts of cloud-init such (setup.py) use these dependencies.
-{% for r in requires %}
+{% for r in buildrequires %}
BuildRequires: {{r}}
{% endfor %}
@@ -57,8 +54,10 @@ Requires: python-argparse
%endif
-# Install 'dynamic' runtime reqs from *requirements.txt and pkg-deps.json
+# Install 'dynamic' runtime reqs from *requirements.txt and pkg-deps.json.
+# Install them as BuildRequires too as they're used for testing.
{% for r in requires %}
+BuildRequires: {{r}}
Requires: {{r}}
{% endfor %}
diff --git a/systemd/cloud-init-local.service.tmpl b/systemd/cloud-init-local.service.tmpl
index ff9c644d..7166f640 100644
--- a/systemd/cloud-init-local.service.tmpl
+++ b/systemd/cloud-init-local.service.tmpl
@@ -5,6 +5,7 @@ Description=Initial cloud-init job (pre-networking)
DefaultDependencies=no
{% endif %}
Wants=network-pre.target
+After=hv_kvp_daemon.service
After=systemd-remount-fs.service
Before=NetworkManager.service
Before=network-pre.target
diff --git a/systemd/cloud-init.service.tmpl b/systemd/cloud-init.service.tmpl
index af6d9a8b..f140344d 100644
--- a/systemd/cloud-init.service.tmpl
+++ b/systemd/cloud-init.service.tmpl
@@ -25,9 +25,11 @@ Before=sshd-keygen.service
Before=sshd.service
{% if variant in ["ubuntu", "unknown", "debian"] %}
Before=sysinit.target
+Before=shutdown.target
Conflicts=shutdown.target
{% endif %}
{% if variant in ["suse"] %}
+Before=shutdown.target
Conflicts=shutdown.target
{% endif %}
Before=systemd-user-sessions.service
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 47e03bd1..2dda9925 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -102,7 +102,13 @@ NETWORK_METADATA = {
"vmId": "ff702a6b-cb6a-4fcd-ad68-b4ce38227642",
"vmScaleSetName": "",
"vmSize": "Standard_DS1_v2",
- "zone": ""
+ "zone": "",
+ "publicKeys": [
+ {
+ "keyData": "key1",
+ "path": "path1"
+ }
+ ]
},
"network": {
"interface": [
@@ -302,7 +308,7 @@ class TestGetMetadataFromIMDS(HttprettyTestCase):
def setUp(self):
super(TestGetMetadataFromIMDS, self).setUp()
- self.network_md_url = dsaz.IMDS_URL + "instance?api-version=2017-12-01"
+ self.network_md_url = dsaz.IMDS_URL + "instance?api-version=2019-06-01"
@mock.patch(MOCKPATH + 'readurl')
@mock.patch(MOCKPATH + 'EphemeralDHCPv4')
@@ -1304,6 +1310,40 @@ scbus-1 on xpt0 bus 0
dsaz.get_hostname(hostname_command=("hostname",))
m_subp.assert_called_once_with(("hostname",), capture=True)
+ @mock.patch(
+ 'cloudinit.sources.helpers.azure.OpenSSLManager.parse_certificates')
+ def test_get_public_ssh_keys_with_imds(self, m_parse_certificates):
+ sys_cfg = {'datasource': {'Azure': {'apply_network_config': True}}}
+ odata = {'HostName': "myhost", 'UserName': "myuser"}
+ data = {
+ 'ovfcontent': construct_valid_ovf_env(data=odata),
+ 'sys_cfg': sys_cfg
+ }
+ dsrc = self._get_ds(data)
+ dsrc.get_data()
+ dsrc.setup(True)
+ ssh_keys = dsrc.get_public_ssh_keys()
+ self.assertEqual(ssh_keys, ['key1'])
+ self.assertEqual(m_parse_certificates.call_count, 0)
+
+ @mock.patch(MOCKPATH + 'get_metadata_from_imds')
+ def test_get_public_ssh_keys_without_imds(
+ self,
+ m_get_metadata_from_imds):
+ m_get_metadata_from_imds.return_value = dict()
+ sys_cfg = {'datasource': {'Azure': {'apply_network_config': True}}}
+ odata = {'HostName': "myhost", 'UserName': "myuser"}
+ data = {
+ 'ovfcontent': construct_valid_ovf_env(data=odata),
+ 'sys_cfg': sys_cfg
+ }
+ dsrc = self._get_ds(data)
+ dsaz.get_metadata_from_fabric.return_value = {'public-keys': ['key2']}
+ dsrc.get_data()
+ dsrc.setup(True)
+ ssh_keys = dsrc.get_public_ssh_keys()
+ self.assertEqual(ssh_keys, ['key2'])
+
class TestAzureBounce(CiTestCase):
@@ -2094,14 +2134,18 @@ class TestAzureDataSourcePreprovisioning(CiTestCase):
md, _ud, cfg, _d = dsa._reprovision()
self.assertEqual(md['local-hostname'], hostname)
self.assertEqual(cfg['system_info']['default_user']['name'], username)
- self.assertEqual(fake_resp.call_args_list,
- [mock.call(allow_redirects=True,
- headers={'Metadata': 'true',
- 'User-Agent':
- 'Cloud-Init/%s' % vs()},
- method='GET',
- timeout=dsaz.IMDS_TIMEOUT_IN_SECONDS,
- url=full_url)])
+ self.assertIn(
+ mock.call(
+ allow_redirects=True,
+ headers={
+ 'Metadata': 'true',
+ 'User-Agent': 'Cloud-Init/%s' % vs()
+ },
+ method='GET',
+ timeout=dsaz.IMDS_TIMEOUT_IN_SECONDS,
+ url=full_url
+ ),
+ fake_resp.call_args_list)
self.assertEqual(m_dhcp.call_count, 2)
m_net.assert_any_call(
broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9',
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
index 5e6d3d2d..5c31b8be 100644
--- a/tests/unittests/test_datasource/test_azure_helper.py
+++ b/tests/unittests/test_datasource/test_azure_helper.py
@@ -609,11 +609,11 @@ class TestWALinuxAgentShim(CiTestCase):
self.GoalState.return_value.container_id = self.test_container_id
self.GoalState.return_value.instance_id = self.test_instance_id
- def test_azure_endpoint_client_uses_certificate_during_report_ready(self):
+ def test_http_client_does_not_use_certificate(self):
shim = wa_shim()
shim.register_with_azure_and_fetch_data()
self.assertEqual(
- [mock.call(self.OpenSSLManager.return_value.certificate)],
+ [mock.call(None)],
self.AzureEndpointHttpClient.call_args_list)
def test_correct_url_used_for_goalstate_during_report_ready(self):
@@ -625,8 +625,11 @@ class TestWALinuxAgentShim(CiTestCase):
[mock.call('http://test_endpoint/machine/?comp=goalstate')],
get.call_args_list)
self.assertEqual(
- [mock.call(get.return_value.contents,
- self.AzureEndpointHttpClient.return_value)],
+ [mock.call(
+ get.return_value.contents,
+ self.AzureEndpointHttpClient.return_value,
+ False
+ )],
self.GoalState.call_args_list)
def test_certificates_used_to_determine_public_keys(self):
@@ -701,7 +704,7 @@ class TestWALinuxAgentShim(CiTestCase):
shim.register_with_azure_and_fetch_data()
shim.clean_up()
self.assertEqual(
- 1, self.OpenSSLManager.return_value.clean_up.call_count)
+ 0, self.OpenSSLManager.return_value.clean_up.call_count)
def test_fetch_goalstate_during_report_ready_raises_exc_on_get_exc(self):
self.AzureEndpointHttpClient.return_value.get \
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 8d7b09c8..3f3fe3eb 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -539,6 +539,87 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
V1_NET_CFG_IPV6,
expected_cfgs=expected_cfgs.copy())
+ def test_vlan_render_unsupported(self):
+ """Render officially unsupported vlan names."""
+ cfg = {
+ 'version': 2,
+ 'ethernets': {
+ 'eth0': {'addresses': ["192.10.1.2/24"],
+ 'match': {'macaddress': "00:16:3e:60:7c:df"}}},
+ 'vlans': {
+ 'infra0': {'addresses': ["10.0.1.2/16"],
+ 'id': 1001, 'link': 'eth0'}},
+ }
+ expected_cfgs = {
+ self.ifcfg_path('eth0'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0
+ HWADDR=00:16:3e:60:7c:df
+ IPADDR=192.10.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ self.ifcfg_path('infra0'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=infra0
+ IPADDR=10.0.1.2
+ NETMASK=255.255.0.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ USERCTL=no
+ VLAN=yes
+ """),
+ self.control_path(): dedent("""\
+ NETWORKING=yes
+ """),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config, cfg,
+ expected_cfgs=expected_cfgs)
+
+ def test_vlan_render(self):
+ cfg = {
+ 'version': 2,
+ 'ethernets': {
+ 'eth0': {'addresses': ["192.10.1.2/24"]}},
+ 'vlans': {
+ 'eth0.1001': {'addresses': ["10.0.1.2/16"],
+ 'id': 1001, 'link': 'eth0'}},
+ }
+ expected_cfgs = {
+ self.ifcfg_path('eth0'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0
+ IPADDR=192.10.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ self.ifcfg_path('eth0.1001'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0.1001
+ IPADDR=10.0.1.2
+ NETMASK=255.255.0.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ USERCTL=no
+ VLAN=yes
+ """),
+ self.control_path(): dedent("""\
+ NETWORKING=yes
+ """),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config, cfg,
+ expected_cfgs=expected_cfgs)
+
class TestNetCfgDistroOpensuse(TestNetCfgDistroBase):
diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py
index 21011204..b2181992 100644
--- a/tests/unittests/test_handler/test_handler_lxd.py
+++ b/tests/unittests/test_handler/test_handler_lxd.py
@@ -214,7 +214,7 @@ class TestLxdMaybeCleanupDefault(t_help.CiTestCase):
"""deletion of network should occur if create is True."""
cc_lxd.maybe_cleanup_default(
net_name=self.defnet, did_init=True, create=True, attach=False)
- m_lxc.assert_called_once_with(["network", "delete", self.defnet])
+ m_lxc.assert_called_with(["network", "delete", self.defnet])
@mock.patch("cloudinit.config.cc_lxd._lxc")
def test_device_removed_if_attach_true(self, m_lxc):
diff --git a/tests/unittests/test_handler/test_handler_power_state.py b/tests/unittests/test_handler/test_handler_power_state.py
index 93b24fdc..4ac49424 100644
--- a/tests/unittests/test_handler/test_handler_power_state.py
+++ b/tests/unittests/test_handler/test_handler_power_state.py
@@ -4,72 +4,102 @@ import sys
from cloudinit.config import cc_power_state_change as psc
+from cloudinit import distros
+from cloudinit import helpers
+
from cloudinit.tests import helpers as t_help
from cloudinit.tests.helpers import mock
class TestLoadPowerState(t_help.TestCase):
+ def setUp(self):
+ super(TestLoadPowerState, self).setUp()
+ cls = distros.fetch('ubuntu')
+ paths = helpers.Paths({})
+ self.dist = cls('ubuntu', {}, paths)
+
def test_no_config(self):
# completely empty config should mean do nothing
- (cmd, _timeout, _condition) = psc.load_power_state({}, 'ubuntu')
+ (cmd, _timeout, _condition) = psc.load_power_state({}, self.dist)
self.assertIsNone(cmd)
def test_irrelevant_config(self):
# no power_state field in config should return None for cmd
(cmd, _timeout, _condition) = psc.load_power_state({'foo': 'bar'},
- 'ubuntu')
+ self.dist)
self.assertIsNone(cmd)
def test_invalid_mode(self):
+
cfg = {'power_state': {'mode': 'gibberish'}}
- self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
+ self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist)
cfg = {'power_state': {'mode': ''}}
- self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
+ self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist)
def test_empty_mode(self):
cfg = {'power_state': {'message': 'goodbye'}}
- self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
+ self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist)
def test_valid_modes(self):
cfg = {'power_state': {}}
for mode in ('halt', 'poweroff', 'reboot'):
cfg['power_state']['mode'] = mode
- check_lps_ret(psc.load_power_state(cfg, 'ubuntu'), mode=mode)
+ check_lps_ret(psc.load_power_state(cfg, self.dist), mode=mode)
def test_invalid_delay(self):
cfg = {'power_state': {'mode': 'poweroff', 'delay': 'goodbye'}}
- self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
+ self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist)
def test_valid_delay(self):
cfg = {'power_state': {'mode': 'poweroff', 'delay': ''}}
for delay in ("now", "+1", "+30"):
cfg['power_state']['delay'] = delay
- check_lps_ret(psc.load_power_state(cfg, 'ubuntu'))
+ check_lps_ret(psc.load_power_state(cfg, self.dist))
def test_message_present(self):
cfg = {'power_state': {'mode': 'poweroff', 'message': 'GOODBYE'}}
- ret = psc.load_power_state(cfg, 'ubuntu')
- check_lps_ret(psc.load_power_state(cfg, 'ubuntu'))
+ ret = psc.load_power_state(cfg, self.dist)
+ check_lps_ret(psc.load_power_state(cfg, self.dist))
self.assertIn(cfg['power_state']['message'], ret[0])
def test_no_message(self):
# if message is not present, then no argument should be passed for it
cfg = {'power_state': {'mode': 'poweroff'}}
- (cmd, _timeout, _condition) = psc.load_power_state(cfg, 'ubuntu')
+ (cmd, _timeout, _condition) = psc.load_power_state(cfg, self.dist)
self.assertNotIn("", cmd)
- check_lps_ret(psc.load_power_state(cfg, 'ubuntu'))
+ check_lps_ret(psc.load_power_state(cfg, self.dist))
self.assertTrue(len(cmd) == 3)
def test_condition_null_raises(self):
cfg = {'power_state': {'mode': 'poweroff', 'condition': None}}
- self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
+ self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist)
def test_condition_default_is_true(self):
cfg = {'power_state': {'mode': 'poweroff'}}
- _cmd, _timeout, cond = psc.load_power_state(cfg, 'ubuntu')
+ _cmd, _timeout, cond = psc.load_power_state(cfg, self.dist)
self.assertEqual(cond, True)
+ def test_freebsd_poweroff_uses_lowercase_p(self):
+ cls = distros.fetch('freebsd')
+ paths = helpers.Paths({})
+ freebsd = cls('freebsd', {}, paths)
+ cfg = {'power_state': {'mode': 'poweroff'}}
+ ret = psc.load_power_state(cfg, freebsd)
+ self.assertIn('-p', ret[0])
+
+ def test_alpine_delay(self):
+ # alpine takes delay in seconds.
+ cls = distros.fetch('alpine')
+ paths = helpers.Paths({})
+ alpine = cls('alpine', {}, paths)
+ cfg = {'power_state': {'mode': 'poweroff', 'delay': ''}}
+ for delay, value in (('now', 0), ("+1", 60), ("+30", 1800)):
+ cfg['power_state']['delay'] = delay
+ ret = psc.load_power_state(cfg, alpine)
+ self.assertEqual('-d', ret[0][1])
+ self.assertEqual(str(value), ret[0][2])
+
class TestCheckCondition(t_help.TestCase):
def cmd_with_exit(self, rc):
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 54cc8469..207e47bb 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1633,7 +1633,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
NM_CONTROLLED=no
ONBOOT=yes
PHYSDEV=bond0
- TYPE=Ethernet
USERCTL=no
VLAN=yes"""),
'ifcfg-br0': textwrap.dedent("""\
@@ -1677,7 +1676,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
NM_CONTROLLED=no
ONBOOT=yes
PHYSDEV=eth0
- TYPE=Ethernet
USERCTL=no
VLAN=yes"""),
'ifcfg-eth1': textwrap.dedent("""\
@@ -2286,7 +2284,6 @@ iface bond0 inet6 static
NM_CONTROLLED=no
ONBOOT=yes
PHYSDEV=en0
- TYPE=Ethernet
USERCTL=no
VLAN=yes"""),
},
@@ -3339,7 +3336,6 @@ USERCTL=no
NM_CONTROLLED=no
ONBOOT=yes
PHYSDEV=eno1
- TYPE=Ethernet
USERCTL=no
VLAN=yes
""")
diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
index c67db436..f01e9b66 100644
--- a/tools/.github-cla-signers
+++ b/tools/.github-cla-signers
@@ -6,8 +6,10 @@ candlerb
dermotbradley
dhensby
eandersson
+emmanuelthome
izzyleung
johnsonshi
+jqueuniet
landon912
lucasmoura
marlluslustosa