diff options
author | Chad Smith <chad.smith@canonical.com> | 2020-09-15 20:19:10 -0600 |
---|---|---|
committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2020-09-16 02:23:11 +0000 |
commit | 3ea0982665e4a50c0b75b408fcc900e294ced925 (patch) | |
tree | 9376f767ae5f93f6b8a41f4d6b4562eea90a76e2 | |
parent | f0ca38837f5d7c5eadf2306493feb183b6f40093 (diff) | |
download | cloud-init-git-3ea0982665e4a50c0b75b408fcc900e294ced925.tar.gz |
20.3-15-g6d332e5c-0ubuntu1 (patches unapplied)
Imported using git-ubuntu import.
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 |