-
Notifications
You must be signed in to change notification settings - Fork 1.1k
networking: refactor wait_for_physdevs from cloudinit.net #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
70d7651
0d4a1e3
5a9a66a
3cd4452
d515731
561eb57
f34e5f5
643fc9f
6df1b3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,11 @@ | ||
| import abc | ||
| import logging | ||
| import os | ||
|
|
||
| from cloudinit import net | ||
| from cloudinit import net, util | ||
|
|
||
|
|
||
| LOG = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| # Type aliases (https://docs.python.org/3/library/typing.html#type-aliases), | ||
|
|
@@ -102,10 +106,72 @@ def is_vlan(self, devname: DeviceName) -> bool: | |
| def master_is_bridge_or_bond(self, devname: DeviceName) -> bool: | ||
| return net.master_is_bridge_or_bond(devname) | ||
|
|
||
| @abc.abstractmethod | ||
| def settle(self, *, exists=None) -> None: | ||
| """Wait for device population in the system to complete. | ||
|
|
||
| :param exists: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My reading of this parameter name made me think initially this was a boolean. This is probably just a poorly named parameter (could have been device_name). For this PR, can we please add typing hints to this exists parameter definition and maybe make to docstring for :param exists mention first that it's a Optional string of a specific DeviceName. If given, only perform.... Would you be opposed to me providing a separate PR to rename that param in udevadm_settle and settle from exists to device_name? There is only one callsite that uses the "exists" param in DataSourceAltCloud.py. |
||
| An optional optimisation. If given, only perform as much of the | ||
| settle process as is required for the given DeviceName to be | ||
| present in the system. (This may include skipping the settle | ||
| process entirely, if the device already exists.) | ||
| :type exists: Optional[DeviceName] | ||
| """ | ||
| pass | ||
|
|
||
| def wait_for_physdevs( | ||
| self, netcfg: NetworkConfig, *, strict: bool = True | ||
| ) -> None: | ||
| return net.wait_for_physdevs(netcfg, strict=strict) | ||
| """Wait for all the physical devices in `netcfg` to exist on the system | ||
|
|
||
| Specifically, this will call `self.settle` 5 times, and check after | ||
| each one if the physical devices are now present in the system. | ||
|
|
||
| :param netcfg: | ||
| The NetworkConfig from which to extract physical devices to wait | ||
| for. | ||
| :param strict: | ||
| Raise a `RuntimeError` if any physical devices are not present | ||
| after waiting. | ||
| """ | ||
| physdevs = self.extract_physdevs(netcfg) | ||
|
|
||
| # set of expected iface names and mac addrs | ||
| expected_ifaces = dict([(iface[0], iface[1]) for iface in physdevs]) | ||
| expected_macs = set(expected_ifaces.keys()) | ||
|
|
||
| # set of current macs | ||
| present_macs = self.get_interfaces_by_mac().keys() | ||
|
|
||
| # compare the set of expected mac address values to | ||
| # the current macs present; we only check MAC as cloud-init | ||
| # has not yet renamed interfaces and the netcfg may include | ||
| # such renames. | ||
| for _ in range(0, 5): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if we want this behavior, but should we parametrize the number of times to retry calling
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, it's easier to read if the value is hard-coded; you don't have to go and find the callers to check if they are passing in something other than the default. Until we have a compelling case to parameterise it (i.e. we want different behaviour for different callers), I'd prefer to leave it as-is. Is that reasonable?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, makes sense |
||
| if expected_macs.issubset(present_macs): | ||
| LOG.debug("net: all expected physical devices present") | ||
| return | ||
|
|
||
| missing = expected_macs.difference(present_macs) | ||
| LOG.debug("net: waiting for expected net devices: %s", missing) | ||
| for mac in missing: | ||
| # trigger a settle, unless this interface exists | ||
| devname = expected_ifaces[mac] | ||
| msg = "Waiting for settle or {} exists".format(devname) | ||
| util.log_time( | ||
| LOG.debug, | ||
| msg, | ||
| func=self.settle, | ||
| kwargs={"exists": devname}, | ||
| ) | ||
|
|
||
| # update present_macs after settles | ||
| present_macs = self.get_interfaces_by_mac().keys() | ||
|
|
||
| msg = "Not all expected physical devices present: %s" % missing | ||
| LOG.warning(msg) | ||
| if strict: | ||
| raise RuntimeError(msg) | ||
|
|
||
|
|
||
| class BSDNetworking(Networking): | ||
|
|
@@ -114,6 +180,10 @@ class BSDNetworking(Networking): | |
| def is_physical(self, devname: DeviceName) -> bool: | ||
| raise NotImplementedError() | ||
|
|
||
| def settle(self, *, exists=None) -> None: | ||
| """BSD has no equivalent to `udevadm settle`; noop.""" | ||
| pass | ||
|
|
||
|
|
||
| class LinuxNetworking(Networking): | ||
| """Implementation of networking functionality common to Linux distros.""" | ||
|
|
@@ -138,3 +208,8 @@ def is_netfail_standby(self, devname: DeviceName) -> bool: | |
|
|
||
| def is_physical(self, devname: DeviceName) -> bool: | ||
| return os.path.exists(net.sys_dev_path(devname, "device")) | ||
|
|
||
| def settle(self, *, exists=None) -> None: | ||
| if exists is not None: | ||
| exists = net.sys_dev_path(exists) | ||
| util.udevadm_settle(exists=exists) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have all the context regarding the creation of that abstraction, but my concern about it is that since this is an abstract method, we cannot ensure that the classes that implement it will follow the
existslogic detailed in the description here.Maybe we can enforce that by turning
settleinto this concrete implementation:Then we would need to make both
find_device_pathandsettle_deviceabstract. However, I am basing that only on what I saw on theLinuxNetworkingimplementation, so I don't know if that is a pattern we really want to enforce.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in the Linux case, we're calling
udevadm settleviautil.udevadm_settle:cloud-init/cloudinit/util.py
Lines 2628 to 2639 in 2b72791
As you can see,
existsis essentially an optimisation: it allows us to return sooner than we might otherwise, but it doesn't materially change the action being performed. I've updated the docstring to try and reflect this more accurately. Is that a reasonable resolution?(As an aside:
BSDNetworkingimplements this as apassbecause there is no equivalent on BSD, so this is really Linux-specific behaviour we're talking about: this implementation will only vary if we have a distro that doesn't use udev, which I infer we don't[0] because this code is run unconditionally today.)[0] Currently; I wonder if Alpine Linux uses udev.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@OddBloke Yes, agreed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alpine uses
eudevwhich isudevcompatible