Merge updates with existing network settings (#3570)

* Preserve network customizations over updates

* Fix issues from testing

* Test case to ensure updates generated correctly
This commit is contained in:
Mike Degatano 2022-04-18 03:49:30 -04:00 committed by GitHub
parent 88795c56f0
commit 32d1296da1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 195 additions and 10 deletions

View File

@ -37,6 +37,17 @@ ATTR_INTERFACE_NAME = "interface-name"
_LOGGER: logging.Logger = logging.getLogger(__name__)
def _merge_settings_attribute(
base_settings: Any, new_settings: Any, attribute: str
) -> None:
"""Merge settings attribute if present."""
if attribute in new_settings:
if attribute in base_settings:
base_settings[attribute].update(new_settings[attribute])
else:
base_settings[attribute] = new_settings[attribute]
class NetworkSetting(DBusInterfaceProxy):
"""Network connection setting object for Network Manager.
@ -97,9 +108,23 @@ class NetworkSetting(DBusInterfaceProxy):
return self.dbus.Settings.Connection.GetSettings()
@dbus_connected
def update(self, settings: Any) -> Awaitable[None]:
async def update(self, settings: Any) -> None:
"""Update connection settings."""
return self.dbus.Settings.Connection.Update(("a{sa{sv}}", settings))
new_settings = (
await self.dbus.Settings.Connection.GetSettings(remove_signature=False)
)[0]
_merge_settings_attribute(new_settings, settings, CONF_ATTR_CONNECTION)
_merge_settings_attribute(new_settings, settings, CONF_ATTR_802_ETHERNET)
_merge_settings_attribute(new_settings, settings, CONF_ATTR_802_WIRELESS)
_merge_settings_attribute(
new_settings, settings, CONF_ATTR_802_WIRELESS_SECURITY
)
_merge_settings_attribute(new_settings, settings, CONF_ATTR_VLAN)
_merge_settings_attribute(new_settings, settings, CONF_ATTR_IPV4)
_merge_settings_attribute(new_settings, settings, CONF_ATTR_IPV6)
return await self.dbus.Settings.Connection.Update(("a{sa{sv}}", new_settings))
@dbus_connected
def delete(self) -> Awaitable[None]:

View File

@ -143,7 +143,9 @@ class DBus:
return signature, arg_list
async def call_dbus(self, method: str, *args: list[Any]) -> str:
async def call_dbus(
self, method: str, *args: list[Any], remove_signature: bool = True
) -> str:
"""Call a dbus method."""
method_parts = method.split(".")
@ -172,7 +174,9 @@ class DBus:
raise DBusFatalError(reply.body[0])
raise DBusFatalError()
return _remove_dbus_signature(reply.body)
if remove_signature:
return _remove_dbus_signature(reply.body)
return reply.body
async def get_properties(self, interface: str) -> dict[str, Any]:
"""Read all properties from interface."""
@ -225,12 +229,14 @@ class DBusCallWrapper:
if interface not in self.dbus.methods:
return DBusCallWrapper(self.dbus, interface)
def _method_wrapper(*args):
def _method_wrapper(*args, remove_signature: bool = True):
"""Wrap method.
Return a coroutine
"""
return self.dbus.call_dbus(interface, *args)
return self.dbus.call_dbus(
interface, *args, remove_signature=remove_signature
)
return _method_wrapper

View File

@ -114,7 +114,9 @@ def dbus() -> DBus:
node = intr.Node.parse(load_fixture(f"{fixture}.{filetype}"))
self._add_interfaces(node)
async def mock_call_dbus(self, method: str, *args: list[Any]):
async def mock_call_dbus(
self, method: str, *args: list[Any], remove_signature: bool = True
):
fixture = self.object_path.replace("/", "_")[1:]
fixture = f"{fixture}-{method.split('.')[-1]}"

View File

@ -0,0 +1,149 @@
"""Test Network Manager Connection object."""
from typing import Any
from unittest.mock import patch
from dbus_next.signature import Variant
from supervisor.coresys import CoreSys
from supervisor.dbus.network.setting.generate import get_connection_from_interface
from supervisor.host.network import Interface
from tests.const import TEST_INTERFACE
async def mock_call_dbus_get_settings_signature(
method: str, *args: list[Any], remove_signature: bool = True
) -> list[dict[str, Any]]:
"""Call dbus method mock for get settings that keeps signature."""
if (
method == "org.freedesktop.NetworkManager.Settings.Connection.GetSettings"
and not remove_signature
):
return [
{
"connection": {
"id": Variant("s", "Wired connection 1"),
"interface-name": Variant("s", "eth0"),
"permissions": Variant("as", []),
"timestamp": Variant("t", 1598125548),
"type": Variant("s", "802-3-ethernet"),
"uuid": Variant("s", "0c23631e-2118-355c-bbb0-8943229cb0d6"),
},
"ipv4": {
"address-data": Variant(
"aa{sv}",
[
{
"address": Variant("s", "192.168.2.148"),
"prefix": Variant("u", 24),
}
],
),
"addresses": Variant("aau", [[2483202240, 24, 16951488]]),
"dns": Variant("au", [16951488]),
"dns-search": Variant("as", []),
"gateway": Variant("s", "192.168.2.1"),
"method": Variant("s", "auto"),
"route-data": Variant(
"aa{sv}",
[
{
"dest": Variant("s", "192.168.122.0"),
"prefix": Variant("u", 24),
"next-hop": Variant("s", "10.10.10.1"),
}
],
),
"routes": Variant("aau", [[8038592, 24, 17435146, 0]]),
},
"ipv6": {
"address-data": Variant("aa{sv}", []),
"addresses": Variant("a(ayuay)", []),
"dns": Variant("au", []),
"dns-search": Variant("as", []),
"method": Variant("s", "auto"),
"route-data": Variant("aa{sv}", []),
"routes": Variant("aau", []),
"addr-gen-mode": Variant("i", 0),
},
"proxy": {},
"802-3-ethernet": {
"auto-negotiate": Variant("b", False),
"mac-address-blacklist": Variant("as", []),
"s390-options": Variant("a{ss}", {}),
},
"802-11-wireless": {"ssid": Variant("ay", bytes([78, 69, 84, 84]))},
}
]
else:
assert method == "org.freedesktop.NetworkManager.Settings.Connection.Update"
assert len(args[0]) == 2
assert args[0][0] == "a{sa{sv}}"
settings = args[0][1]
assert "connection" in settings
assert settings["connection"]["id"] == Variant("s", "Supervisor eth0")
assert settings["connection"]["interface-name"] == Variant("s", "eth0")
assert settings["connection"]["uuid"] == Variant(
"s", "0c23631e-2118-355c-bbb0-8943229cb0d6"
)
assert "ipv4" in settings
assert settings["ipv4"]["gateway"] == Variant("s", "192.168.2.1")
assert settings["ipv4"]["method"] == Variant("s", "auto")
assert settings["ipv4"]["dns"] == Variant("au", [16951488])
assert len(settings["ipv4"]["address-data"].value) == 1
assert settings["ipv4"]["address-data"].value[0]["address"] == Variant(
"s", "192.168.2.148"
)
assert settings["ipv4"]["address-data"].value[0]["prefix"] == Variant("u", 24)
assert len(settings["ipv4"]["route-data"].value) == 1
assert settings["ipv4"]["route-data"].value[0]["dest"] == Variant(
"s", "192.168.122.0"
)
assert settings["ipv4"]["route-data"].value[0]["prefix"] == Variant("u", 24)
assert settings["ipv4"]["route-data"].value[0]["next-hop"] == Variant(
"s", "10.10.10.1"
)
assert settings["ipv4"]["routes"] == Variant(
"aau", [[8038592, 24, 17435146, 0]]
)
assert "ipv6" in settings
assert settings["ipv6"]["method"] == Variant("s", "auto")
assert settings["ipv6"]["addr-gen-mode"] == Variant("i", 0)
assert "proxy" in settings
assert "802-3-ethernet" in settings
assert settings["802-3-ethernet"]["auto-negotiate"] == Variant("b", False)
assert "802-11-wireless" in settings
assert settings["802-11-wireless"]["ssid"] == Variant(
"ay", bytes([78, 69, 84, 84])
)
assert "mode" not in settings["802-11-wireless"]
assert "powersave" not in settings["802-11-wireless"]
assert "802-11-wireless-security" not in settings
assert "vlan" not in settings
async def test_update(coresys: CoreSys):
"""Test network manager update."""
await coresys.dbus.network.interfaces[TEST_INTERFACE].connect()
interface = Interface.from_dbus_interface(
coresys.dbus.network.interfaces[TEST_INTERFACE]
)
conn = get_connection_from_interface(
interface,
name=coresys.dbus.network.interfaces[TEST_INTERFACE].settings.connection.id,
uuid=coresys.dbus.network.interfaces[TEST_INTERFACE].settings.connection.uuid,
)
with patch.object(
coresys.dbus.network.interfaces[TEST_INTERFACE].settings.dbus,
"call_dbus",
new=mock_call_dbus_get_settings_signature,
):
await coresys.dbus.network.interfaces[TEST_INTERFACE].settings.update(conn)

View File

@ -15,8 +15,10 @@
"dns-search": [],
"gateway": "192.168.2.1",
"method": "auto",
"route-data": [],
"routes": []
"route-data": [
{ "dest": "192.168.122.0", "prefix": 24, "next-hop": "10.10.10.1" }
],
"routes": [[8038592, 24, 17435146, 0]]
},
"ipv6": {
"address-data": [],
@ -25,7 +27,8 @@
"dns-search": [],
"method": "auto",
"route-data": [],
"routes": []
"routes": [],
"addr-gen-mode": 0
},
"proxy": {},
"802-3-ethernet": {