-
Notifications
You must be signed in to change notification settings - Fork 205
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
New 'netplan status' CLI #290
Conversation
917a3f1
to
a44c6b6
Compare
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 think the most critical issue I've found is in the Interface class' constructor. It crashes in systems where network-manager is not installed, like LXD images (at least kinetic).
netplan/cli/commands/status.py
Outdated
import subprocess | ||
import sys | ||
import yaml | ||
import netplan.cli.utils as utils |
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.
nitpick: We probably should follow PEP8 and group imports in this order:
- standard library import
- third party imports
- application imports
netplan/cli/commands/status.py
Outdated
self.adminstate: str = 'UP' if 'UP' in ip['flags'] else 'DOWN' | ||
self.operstate: str = ip['operstate'].upper() | ||
self.macaddress: str = None | ||
if 'address' in ip and len(ip['address']) == 17: # 6 byte MAC |
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.
suggestion: Magic numbers look better as 'constants' with meaningful names, or we could even have a little private method returning the MAC address or None.
@property | ||
def netdef_id(self) -> str: | ||
if self.backend == 'networkd': | ||
return self.nd.get('NetworkFile', '').split( |
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.
question: If the goal here is to get the interface name, wouldn't it be better to just use the Name attribute?
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.
No. This is not about getting the interface name, but rather the Netplan netdef_id
(which COULD be equal to the interface name, but can also be different). E.g. on my system I have this, where "usbC" is the NetdefID inside my netplan YAML config, while "lan0" is the interface name.
{
"Index" : 14,
"Name" : "lan0",
"AlternativeNames" : [],
"Type" : "ether",
"Driver" : "ax88179_178a",
"SetupState" : "configured",
"OperationalState" : "routable",
"CarrierState" : "carrier",
"AddressState" : "routable",
"IPv4AddressState" : "routable",
"IPv6AddressState" : "routable",
"OnlineState" : "online",
"NetworkFile" : "/run/systemd/network/10-netplan-usbC.network",
"LinkFile" : "/run/systemd/network/10-netplan-usbC.link",
"Path" : "pci-0000:00:0d.0-usb-0:3.2.1:1.0",
"Vendor" : "ASIX Electronics Corp.",
"Model" : "AX88179 Gigabit Ethernet"
}
netplan/cli/commands/status.py
Outdated
if 'flags' not in extra or 'link' not in extra['flags']: | ||
non_local_ips.append(ip) | ||
default_routes = [x for x in itf.routes if x.get('to', None) == 'default'] | ||
if len(non_local_ips) > 0 and len(default_routes) > 0 and len(itf.dns_addresses) > 0: |
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.
nitpick: empty lists will evaluate to False so this could be simplified to if list1 and list2 and list3
.
netplan/cli/commands/status.py
Outdated
dns_addr = ns.get('addresses', []) | ||
dns_mode = ns.get('mode') | ||
dns_search = ns.get('search', []) | ||
if len(dns_addr) > 0: |
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.
nitpick: empty lists evaluate to False, could be simplified to if dns_addr:
. There are few more instances like this across the code.
# due to hard package dependencies | ||
iproute2 = self.query_iproute2() | ||
networkd = self.query_networkd() | ||
if not iproute2 or not networkd: |
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.
question: Is it expected to fail on systems that don't use networkd? It's stopping here on my system (standard kinetic upgraded from jammy).
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.
systemd[-networkd] should be installed on any relevant system, as it is a dependency of netplan.io. IIRC I designed this new CLI in a way that it would assume networkd to be available... But maybe I should double-check if networkd is actually up and running on a system which does not necessarily depend on it (like a standard Ubuntu Desktop system).
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.
Dang. I can confirm that systemd-networkd is installed but not running on a default Ubuntu Desktop installation. Let's see what I can do about that (maybe starting the service before trying to query it).
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.
Checking the activity status of systemd-networkd.service
and starting it if needed solves this issue. We assume systemd-networkd to be installed by default (it's part of the systemd
package in Ubuntu).
One drawback: netplan status
would be able to run without sudo privileges, if not for this reason of starting a system service... But maybe that's something to solve at a later time, as Netplan is a system/admin tool and requires root privileges for most of it's actions anyways.
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.
Right. So, assuming sd-networkd is a hard dependency for netplan status, and I don't have a strong opinion on that, we should check first if we can start it. If it's masked for example netplan status will crash.
It would be nice to show a nice message like "netplan status depends on networkd" if we can't start it. A good thing is that once it's running one can call netplan status as a non-privileged user.
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.
Or maybe we could just tell the user that networkd is not running and ask them to start it instead of trying to do that inside netplan...
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'd like to have it working "out of the box" on a default Ubuntu Desktop installation, that's why I'd like to automatically have it started if needed.
But checking if systemd-networkd.service is masked is a fair point. We'll now be bailing out if networkd is masked with an error message notifying the user to start networkd. If it's just disabled OTOH we'll try to start it automatically.
Netplan's 'dbus' tests module was in conflict with the system's global 'dbus' module (i.e. D-Bus bindings), leading to failing tests. We rename the tests to 'netplan_dbus' to avoid this. Cleanup pytest-3 deprecation warnings while on it
We make use of the previously assembled structural JSON data, to make sure we always present the same data in JSON/YAML and human readable format and don't miss anything in the structual output.
Thank you very much for the review @daniloegea I think I addressed all of your remarks. PTAL. |
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.
The output looks really nice. Now it's working on the scenarios I've tested before.
Found only 2 things we probably should address: we should at least check if networkd is not masked before trying to start it to avoid a crash and disable the conversion of sequences like :ab:
to emojis.
netplan/cli/commands/status.py
Outdated
def command(self): | ||
networkd: str = subprocess.check_output(['networkctl', '--json=short'], | ||
universal_newlines=True) | ||
networkd_data = yaml.safe_load(networkd) |
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.
thought: hmm using the yaml module to load a JSON string, not a problem just counterintuitive. Anyway, the result of json.loads(networkd)
is exactly the same.
logging.debug('Cannot query resolved DNS data: {}'.format(str(e))) | ||
return (addresses, search) | ||
|
||
def pretty_print(self, data: JSON, total: int, _console_width=None) -> None: |
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've found by accident that sequences like :ab:
and :1234:
are being converted to emojis:
● 13: br123 bridge DOWN/UP (NetworkManager: br123)
MAC Address: ee:21:ad:24:f9:71
Addresses: a🆎b🆎a🔢a:abcd/128
Routes: a🆎b🆎a🔢a:abcd metric 426
netplan/cli/commands/status.py
Outdated
self.routes.append(elem) | ||
|
||
self.addresses: list = None | ||
if 'addr_info' in ip and ip['addr_info']: |
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.
nitpick (no need to change): since python 3.8 this pattern can be simplified a bit with the := operator
if addr_info := ip.get('addr_info'):
for addr in addr_info:
# due to hard package dependencies | ||
iproute2 = self.query_iproute2() | ||
networkd = self.query_networkd() | ||
if not iproute2 or not networkd: |
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.
Or maybe we could just tell the user that networkd is not running and ask them to start it instead of trying to do that inside netplan...
Thanks for another sanity check and spotting those issues! |
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.
lgtm
Add python3-dbus and python3-rich[1] to RDEPENDS. [1] canonical/netplan#290 Signed-off-by: Yi Zhao <yi.zhao@windriver.com> Signed-off-by: Khem Raj <raj.khem@gmail.com>
Add python3-dbus and python3-rich[1] to RDEPENDS. [1] canonical/netplan#290 Signed-off-by: Yi Zhao <yi.zhao@windriver.com> Signed-off-by: Khem Raj <raj.khem@gmail.com>
Add python3-dbus and python3-rich[1] to RDEPENDS. [1] canonical/netplan#290 Signed-off-by: Yi Zhao <yi.zhao@windriver.com> Signed-off-by: Khem Raj <raj.khem@gmail.com>
Description
Implement new, initial
netplan status
command, according to spec FO049, which will show a system's current networking state, querying data from multiple backends (e.g. sd-networkd, NetworkManager, iproute2, DBus/sd-resolved, ...) and connecting the interface data with its corresponding Netplan/Netdef ID.A new depdendency
python3-rich
is used for pretty printing the output to a terminal, in addition to structural JSON or YAML output. Usage:Example output (non-pretty, as we're missing colors/bold text in this GitHub PR view):
The code in this branch can be executed like this:
Checklist
make check
successfully.make check-coverage
).