Files
supervisor/tests/dbus_service_mocks/network_manager.py
Stefan Agner 8a95113ebd Improve VLAN configuration (#6094)
* Fix NetworkManager connection name for VLANs

The connection name for VLANs should include the parent interface name
for better identification. This was originally the intention, but the
interface object's name property was used which appears empty at that
point.

* Disallow creating multiple connections for the same VLAN id

Only allow a single connection per interface and VLAN id. The regular
network commands can be used to alter the configuration.

* Fix pytest

* Simply connection id name generation

Always rely on the Supervisor interface representation's name attribute
to generate the NetworkManager connection id. Make sure that the name
is correctly set when creating VLAN interfaces as well.

* Special case VLAN configuration

We can't use the match information when comparing Supervisor interface
representation with D-Bus representations. Special case VLAN and
compare using VLAN ID and parent interface.

Note that this currently compares connection UUID of the parent
interface.

* Fix pytest

* Separate VLAN creation logic from apply_changes

Apply changes is really all about updating the NetworkManager settings
of a particular network interface. The base in apply_changes() is
NetworkInterface class, which is the NetworkManager Device abstraction.
All physical interfaces have such a Device hence it is always present.

The only exception is when creating a VLAN: Since it is a virtual
device, there is no device when creating a VLAN.

This separate the two cases. This makes it much easier to reason if
a VLAN already exists or not, and to handle the case where a VLAN
needs to be created.

For all other network interfaces, the apply_changes() method can
now rely on the presence of the NetworkInterface Device abstraction.

* Add VLAN test interface and VLAN exists test

Add a test which checks that an error gets raised when a VLAN for a
particular interface/id combination already exists.

* Address pylint

* Fix test_ignore_veth_only_changes pytest

* Make VLAN interface disabled to avoid test issues

* Reference setting 38 in mocked connection

* Make sure interface type matches

Require a interface type match before doing any comparision.

* Add Supervisor host network configuration tests

* Fix device type checking

* Fix pytest

* Fix tests by taking VLAN interface into account

* Fix test_load_with_network_connection_issues

This seems like a hack, but it turns out that the additional active
connection caused coresys.host.network.update() to be called, which
implicitly "fake" activated the connection. Now it seems that our
mocking causes IPv4 gateway to be set.

So in a way, the test checked a particular mock behavior instead of
actual intention.

The crucial part of this test is that we make sure the settings remain
unchanged. This is done by ensuring that the the method is still auto.

* Fix test_check_network_interface_ipv4.py

Now that we have the VLAN interface active too it will raise an issue
as well.

* Apply suggestions from code review

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>

* Fix ruff check issue

---------

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
2025-08-22 11:09:39 +02:00

347 lines
11 KiB
Python

"""Mock of Network Manager service."""
from dbus_fast import DBusError
from dbus_fast.service import PropertyAccess, dbus_property, signal
from .base import DBusServiceMock, dbus_method
BUS_NAME = "org.freedesktop.NetworkManager"
def setup(object_path: str | None = None) -> DBusServiceMock:
"""Create dbus mock object."""
return NetworkManager()
class NetworkManager(DBusServiceMock):
"""Network Manager mock.
gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager
"""
interface = "org.freedesktop.NetworkManager"
object_path = "/org/freedesktop/NetworkManager"
version = "1.22.10"
connectivity_check_enabled = True
connectivity = 4
devices = [
"/org/freedesktop/NetworkManager/Devices/1",
"/org/freedesktop/NetworkManager/Devices/3",
"/org/freedesktop/NetworkManager/Devices/38",
]
@dbus_property(access=PropertyAccess.READ)
def Devices(self) -> "ao":
"""Get Devices."""
return self.devices
@dbus_property(access=PropertyAccess.READ)
def AllDevices(self) -> "ao":
"""Get AllDevices."""
return [
"/org/freedesktop/NetworkManager/Devices/1",
"/org/freedesktop/NetworkManager/Devices/2",
"/org/freedesktop/NetworkManager/Devices/3",
"/org/freedesktop/NetworkManager/Devices/38",
]
@dbus_property(access=PropertyAccess.READ)
def Checkpoints(self) -> "ao":
"""Get Checkpoints."""
return []
@dbus_property(access=PropertyAccess.READ)
def NetworkingEnabled(self) -> "b":
"""Get NetworkingEnabled."""
return True
@dbus_property()
def WirelessEnabled(self) -> "b":
"""Get WirelessEnabled."""
return True
@WirelessEnabled.setter
def WirelessEnabled(self, value: "b"):
"""Set WirelessEnabled."""
self.emit_properties_changed({"WirelessEnabled": value})
@dbus_property(access=PropertyAccess.READ)
def WirelessHardwareEnabled(self) -> "b":
"""Get WirelessHardwareEnabled."""
return True
@dbus_property()
def WwanEnabled(self) -> "b":
"""Get WwanEnabled."""
return True
@WwanEnabled.setter
def WwanEnabled(self, value: "b"):
"""Set WwanEnabled."""
self.emit_properties_changed({"WwanEnabled": value})
@dbus_property(access=PropertyAccess.READ)
def WwanHardwareEnabled(self) -> "b":
"""Get WwanHardwareEnabled."""
return True
@dbus_property()
def WimaxEnabled(self) -> "b":
"""Get WimaxEnabled."""
return False
@WimaxEnabled.setter
def WimaxEnabled(self, value: "b"):
"""Set WimaxEnabled."""
self.emit_properties_changed({"WimaxEnabled": value})
@dbus_property(access=PropertyAccess.READ)
def WimaxHardwareEnabled(self) -> "b":
"""Get WimaxHardwareEnabled."""
return False
@dbus_property(access=PropertyAccess.READ)
def ActiveConnections(self) -> "ao":
"""Get ActiveConnections."""
return [
"/org/freedesktop/NetworkManager/ActiveConnection/1",
"/org/freedesktop/NetworkManager/ActiveConnection/38",
]
@dbus_property(access=PropertyAccess.READ)
def PrimaryConnection(self) -> "o":
"""Get PrimaryConnection."""
return "/org/freedesktop/NetworkManager/ActiveConnection/1"
@dbus_property(access=PropertyAccess.READ)
def PrimaryConnectionType(self) -> "s":
"""Get PrimaryConnectionType."""
return "802-3-ethernet"
@dbus_property(access=PropertyAccess.READ)
def Metered(self) -> "u":
"""Get Metered."""
return 4
@dbus_property(access=PropertyAccess.READ)
def ActivatingConnection(self) -> "o":
"""Get ActivatingConnection."""
return "/"
@dbus_property(access=PropertyAccess.READ)
def Startup(self) -> "b":
"""Get Startup."""
return False
@dbus_property(access=PropertyAccess.READ)
def Version(self) -> "s":
"""Get Version."""
return self.version
@dbus_property(access=PropertyAccess.READ)
def Capabilities(self) -> "au":
"""Get Capabilities."""
return [1]
@dbus_property(access=PropertyAccess.READ)
def State(self) -> "u":
"""Get State."""
return 70
@dbus_property(access=PropertyAccess.READ)
def Connectivity(self) -> "u":
"""Get Connectivity."""
return self.connectivity
@dbus_property(access=PropertyAccess.READ)
def ConnectivityCheckAvailable(self) -> "b":
"""Get ConnectivityCheckAvailable."""
return True
@dbus_property()
def ConnectivityCheckEnabled(self) -> "b":
"""Get ConnectivityCheckEnabled."""
return self.connectivity_check_enabled
@ConnectivityCheckEnabled.setter
def ConnectivityCheckEnabled(self, value: "b"):
"""Set ConnectivityCheckEnabled."""
self.emit_properties_changed({"ConnectivityCheckEnabled": value})
@dbus_property(access=PropertyAccess.READ)
def ConnectivityCheckUri(self) -> "s":
"""Get ConnectivityCheckUri."""
return "http://connectivity-check.ubuntu.com/"
@dbus_property()
def GlobalDnsConfiguration(self) -> "a{sv}":
"""Get GlobalDnsConfiguration."""
return {}
@GlobalDnsConfiguration.setter
def GlobalDnsConfiguration(self, value: "a{sv}"):
"""Set GlobalDnsConfiguration."""
self.emit_properties_changed({"GlobalDnsConfiguration": value})
@signal()
def CheckPermissions(self) -> None:
"""Signal CheckPermissions."""
# These signals all seem redundant. Their respective properties fire PropertiesChanged signals
@signal()
def StateChanged(self) -> "u":
"""Signal StateChanged."""
return 70
@signal()
def DeviceAdded(self) -> "o":
"""Signal DeviceAdded."""
return "/org/freedesktop/NetworkManager/Devices/2"
@signal()
def DeviceRemoved(self) -> "o":
"""Signal DeviceRemoved."""
return "/org/freedesktop/NetworkManager/Devices/2"
@dbus_method()
def Reload(self, flags: "u") -> None:
"""Do Reload method."""
@dbus_method()
def GetDevices(self) -> "ao":
"""Do GetDevices method."""
return self.Devices
@dbus_method()
def GetAllDevices(self) -> "ao":
"""Do GetAllDevices method."""
return self.AllDevices
@dbus_method()
def GetDeviceByIpIface(self, iface: "s") -> "o":
"""Do GetDeviceByIpIface method."""
return "/org/freedesktop/NetworkManager/Devices/1"
@dbus_method()
def ActivateConnection(
self, connection: "o", device: "o", specific_object: "o"
) -> "o":
"""Do ActivateConnection method."""
return "/org/freedesktop/NetworkManager/ActiveConnection/1"
@dbus_method()
def AddAndActivateConnection(
self, connection: "a{sa{sv}}", device: "o", speciic_object: "o"
) -> "oo":
"""Do AddAndActivateConnection method."""
if connection["connection"]["type"].value == "802-11-wireless":
if "802-11-wireless" not in connection:
raise DBusError(
"org.freedesktop.NetworkManager.Device.InvalidConnection",
"A 'wireless' setting is required if no AP path was given.",
)
if "ssid" not in connection["802-11-wireless"]:
raise DBusError(
"org.freedesktop.NetworkManager.Device.InvalidConnection",
"A 'wireless' setting with a valid SSID is required if no AP path was given.",
)
return [
"/org/freedesktop/NetworkManager/Settings/1",
"/org/freedesktop/NetworkManager/ActiveConnection/1",
]
@dbus_method()
def AddAndActivateConnection2(
self,
connection: "a{sa{sv}}",
device: "o",
speciic_object: "o",
options: "a{sv}",
) -> "ooa{sv}":
"""Do AddAndActivateConnection2 method."""
return [
"/org/freedesktop/NetworkManager/Settings/1",
"/org/freedesktop/NetworkManager/ActiveConnection/1",
{},
]
@dbus_method()
def DeactivateConnection(self, active_connection: "o") -> None:
"""Do DeactivateConnection method."""
@dbus_method()
def Sleep(self, sleep: "b") -> None:
"""Do Sleep method."""
@dbus_method()
def Enable(self, enable: "b") -> None:
"""Do Enable method."""
@dbus_method()
def GetPermissions(self) -> "a{ss}":
"""Do GetPermissions method."""
return {
"org.freedesktop.NetworkManager.checkpoint-rollback": "yes",
"org.freedesktop.NetworkManager.enable-disable-connectivity-check": "yes",
"org.freedesktop.NetworkManager.enable-disable-network": "yes",
"org.freedesktop.NetworkManager.enable-disable-statistics": "yes",
"org.freedesktop.NetworkManager.enable-disable-wifi": "yes",
"org.freedesktop.NetworkManager.enable-disable-wimax": "yes",
"org.freedesktop.NetworkManager.enable-disable-wwan": "yes",
"org.freedesktop.NetworkManager.network-control": "yes",
"org.freedesktop.NetworkManager.reload": "yes",
"org.freedesktop.NetworkManager.settings.modify.global-dns": "yes",
"org.freedesktop.NetworkManager.settings.modify.hostname": "yes",
"org.freedesktop.NetworkManager.settings.modify.own": "yes",
"org.freedesktop.NetworkManager.settings.modify.system": "yes",
"org.freedesktop.NetworkManager.sleep-wake": "yes",
"org.freedesktop.NetworkManager.wifi.scan": "yes",
"org.freedesktop.NetworkManager.wifi.share.open": "yes",
"org.freedesktop.NetworkManager.wifi.share.protected": "yes",
}
@dbus_method()
def SetLogging(self, level: "s", domains: "s") -> None:
"""Do SetLogging method."""
@dbus_method()
def GetLogging(self) -> "ss":
"""Do GetLogging method."""
return [
"INFO",
"PLATFORM,RFKILL,ETHER,WIFI,BT,MB,DHCP4,DHCP6,PPP,IP4,IP6,AUTOIP4,DNS,VPN,"
"SHARING,SUPPLICANT,AGENTS,SETTINGS,SUSPEND,CORE,DEVICE,OLPC,INFINIBAND,"
"FIREWALL,ADSL,BOND,VLAN,BRIDGE,TEAM,CONCHECK,DCB,DISPATCH,AUDIT,SYSTEMD,PROXY",
]
@dbus_method()
def CheckConnectivity(self) -> "u":
"""Do CheckConnectivity method."""
return self.Connectivity
@dbus_method()
def state(self) -> "u":
"""Do state method."""
return self.State
@dbus_method()
def CheckpointCreate(self, devices: "ao", rollback_timeout: "u", flags: "u") -> "o":
"""Do CheckpointCreate method."""
return "/org/freedesktop/NetworkManager/Checkpoint/1"
@dbus_method()
def CheckpointDestroy(self, checkpoint: "o") -> None:
"""Do CheckpointDestroy method."""
@dbus_method()
def CheckpointRollback(self, checkpoint: "o") -> "a{su}":
"""Do CheckpointRollback method."""
return {}
@dbus_method()
def CheckpointAdjustRollbackTimeout(
self, checkpoint: "o", add_timeout: "u"
) -> None:
"""Do CheckpointAdjustRollbackTimeout method."""