Split payload so we can set auto without IP configuration (#1982)

* Guard for no interfaces

* Host reload on update

* Extract payload

* Check eth and wifi interfaces with a valid ip4 config

* Add tests

* Fix tests

* Move to enum
This commit is contained in:
Joakim Sørensen 2020-08-28 10:40:50 +02:00 committed by GitHub
parent a2821a98ad
commit 8d3694884d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 143 additions and 42 deletions

View File

@ -36,6 +36,8 @@ setup(
"supervisor.addons", "supervisor.addons",
"supervisor.api", "supervisor.api",
"supervisor.dbus", "supervisor.dbus",
"supervisor.dbus.payloads",
"supervisor.dbus.network",
"supervisor.discovery", "supervisor.discovery",
"supervisor.discovery.services", "supervisor.discovery.services",
"supervisor.services", "supervisor.services",

View File

@ -93,6 +93,6 @@ class APINetwork(CoreSysAttributes):
self.sys_host.network.interfaces[req_interface].update_settings(**args) self.sys_host.network.interfaces[req_interface].update_settings(**args)
) )
await asyncio.shield(self.sys_host.network.update()) await asyncio.shield(self.sys_host.reload())
return await asyncio.shield(self.interface_info(request)) return await asyncio.shield(self.interface_info(request))

View File

@ -72,3 +72,10 @@ class InterfaceMethodSimple(str, Enum):
DHCP = "dhcp" DHCP = "dhcp"
STATIC = "static" STATIC = "static"
class ConnectionType(str, Enum):
"""Connection type."""
ETHERNET = "802-3-ethernet"
WIRELESS = "802-11-wireless"

View File

@ -2,13 +2,14 @@
import logging import logging
from typing import Dict, Optional from typing import Dict, Optional
from ...exceptions import DBusError, DBusInterfaceError from ...exceptions import DBusError, DBusFatalError, DBusInterfaceError
from ...utils.gdbus import DBus from ...utils.gdbus import DBus
from ..const import ( from ..const import (
DBUS_ATTR_ACTIVE_CONNECTIONS, DBUS_ATTR_ACTIVE_CONNECTIONS,
DBUS_ATTR_PRIMARY_CONNECTION, DBUS_ATTR_PRIMARY_CONNECTION,
DBUS_NAME_NM, DBUS_NAME_NM,
DBUS_OBJECT_NM, DBUS_OBJECT_NM,
ConnectionType,
) )
from ..interface import DBusInterface from ..interface import DBusInterface
from ..utils import dbus_connected from ..utils import dbus_connected
@ -65,11 +66,17 @@ class NetworkManager(DBusInterface):
await interface.connect(self.dbus, connection) await interface.connect(self.dbus, connection)
if not interface.connection.default: if interface.connection.type not in [
ConnectionType.ETHERNET,
ConnectionType.WIRELESS,
]:
continue continue
try: try:
await interface.connection.update_information() await interface.connection.update_information()
except IndexError: except (IndexError, DBusFatalError):
continue
if not interface.connection.ip4_config:
continue continue
if interface.connection.object_path == data.get( if interface.connection.object_path == data.get(

View File

@ -22,6 +22,7 @@ from ..const import (
DBUS_NAME_DEVICE, DBUS_NAME_DEVICE,
DBUS_NAME_IP4CONFIG, DBUS_NAME_IP4CONFIG,
DBUS_NAME_NM, DBUS_NAME_NM,
DBUS_OBJECT_BASE,
) )
from .configuration import ( from .configuration import (
AddressData, AddressData,
@ -91,6 +92,9 @@ class NetworkConnection(NetworkAttributes):
async def update_information(self): async def update_information(self):
"""Update the information for childs .""" """Update the information for childs ."""
if self._properties[DBUS_ATTR_IP4CONFIG] == DBUS_OBJECT_BASE:
return
settings = await DBus.connect( settings = await DBus.connect(
DBUS_NAME_NM, self._properties[DBUS_ATTR_CONNECTION] DBUS_NAME_NM, self._properties[DBUS_ATTR_CONNECTION]
) )

View File

@ -1,5 +1,4 @@
"""NetworkInterface object for Network Manager.""" """NetworkInterface object for Network Manager."""
from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_GATEWAY, ATTR_METHOD, ATTR_PREFIX
from ...utils.gdbus import DBus from ...utils.gdbus import DBus
from ..const import ( from ..const import (
DBUS_NAME_CONNECTION_ACTIVE, DBUS_NAME_CONNECTION_ACTIVE,
@ -7,8 +6,8 @@ from ..const import (
DBUS_OBJECT_BASE, DBUS_OBJECT_BASE,
InterfaceMethod, InterfaceMethod,
) )
from ..payloads.interface_update import interface_update_payload
from .connection import NetworkConnection from .connection import NetworkConnection
from .utils import ip2int
class NetworkInterface: class NetworkInterface:
@ -85,42 +84,9 @@ class NetworkInterface:
async def update_settings(self, **kwargs) -> None: async def update_settings(self, **kwargs) -> None:
"""Update IP configuration used for this interface.""" """Update IP configuration used for this interface."""
if kwargs.get(ATTR_DNS): payload = interface_update_payload(self, **kwargs)
kwargs[ATTR_DNS] = [ip2int(x.strip()) for x in kwargs[ATTR_DNS]]
if kwargs.get(ATTR_METHOD): await self.connection.settings.dbus.Settings.Connection.Update(payload)
kwargs[ATTR_METHOD] = (
InterfaceMethod.MANUAL
if kwargs[ATTR_METHOD] == "static"
else InterfaceMethod.AUTO
)
if kwargs.get(ATTR_ADDRESS):
if "/" in kwargs[ATTR_ADDRESS]:
kwargs[ATTR_PREFIX] = kwargs[ATTR_ADDRESS].split("/")[-1]
kwargs[ATTR_ADDRESS] = kwargs[ATTR_ADDRESS].split("/")[0]
kwargs[ATTR_METHOD] = InterfaceMethod.MANUAL
await self.connection.settings.dbus.Settings.Connection.Update(
f"""{{
'connection':
{{
'id': <'{self.id}'>,
'type': <'{self.type}'>
}},
'ipv4':
{{
'method': <'{kwargs.get(ATTR_METHOD, self.method)}'>,
'dns': <[{",".join([f"uint32 {x}" for x in kwargs.get(ATTR_DNS, self.nameservers)])}]>,
'address-data': <[
{{
'address': <'{kwargs.get(ATTR_ADDRESS, self.ip_address)}'>,
'prefix': <uint32 {kwargs.get(ATTR_PREFIX, self.prefix)}>
}}]>,
'gateway': <'{kwargs.get(ATTR_GATEWAY, self.gateway)}'>
}}
}}"""
)
await self.nm_dbus.ActivateConnection( await self.nm_dbus.ActivateConnection(
self.connection.settings.dbus.object_path, self.connection.settings.dbus.object_path,

View File

@ -0,0 +1 @@
"""Init file for DBUS payloads."""

View File

@ -0,0 +1,55 @@
"""Payload generators for DBUS communication."""
from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_GATEWAY, ATTR_METHOD, ATTR_PREFIX
from ..const import InterfaceMethod
from ..network.utils import ip2int
def interface_update_payload(interface, **kwargs) -> str:
"""Generate a payload for network interface update."""
if kwargs.get(ATTR_DNS):
kwargs[ATTR_DNS] = [ip2int(x.strip()) for x in kwargs[ATTR_DNS]]
if kwargs.get(ATTR_METHOD):
kwargs[ATTR_METHOD] = (
InterfaceMethod.MANUAL
if kwargs[ATTR_METHOD] == "static"
else InterfaceMethod.AUTO
)
if kwargs.get(ATTR_ADDRESS):
if "/" in kwargs[ATTR_ADDRESS]:
kwargs[ATTR_PREFIX] = kwargs[ATTR_ADDRESS].split("/")[-1]
kwargs[ATTR_ADDRESS] = kwargs[ATTR_ADDRESS].split("/")[0]
kwargs[ATTR_METHOD] = InterfaceMethod.MANUAL
if kwargs.get(ATTR_METHOD) == "auto":
return f"""{{
'connection':
{{
'id': <'{interface.id}'>,
'type': <'{interface.type}'>
}},
'ipv4':
{{
'method': <'{InterfaceMethod.AUTO}'>
}}
}}"""
return f"""{{
'connection':
{{
'id': <'{interface.id}'>,
'type': <'{interface.type}'>
}},
'ipv4':
{{
'method': <'{InterfaceMethod.MANUAL}'>,
'dns': <[{",".join([f"uint32 {x}" for x in kwargs.get(ATTR_DNS, interface.nameservers)])}]>,
'address-data': <[
{{
'address': <'{kwargs.get(ATTR_ADDRESS, interface.ip_address)}'>,
'prefix': <uint32 {kwargs.get(ATTR_PREFIX, interface.prefix)}>
}}]>,
'gateway': <'{kwargs.get(ATTR_GATEWAY, interface.gateway)}'>
}}
}}"""

View File

@ -69,7 +69,7 @@ class HostManager(CoreSysAttributes):
[HostFeature.REBOOT, HostFeature.SHUTDOWN, HostFeature.SERVICES] [HostFeature.REBOOT, HostFeature.SHUTDOWN, HostFeature.SERVICES]
) )
if self.sys_dbus.network.is_connected: if self.sys_dbus.network.is_connected and self.sys_dbus.network.interfaces:
features.append(HostFeature.NETWORK) features.append(HostFeature.NETWORK)
if self.sys_dbus.hostname.is_connected: if self.sys_dbus.hostname.is_connected:

View File

@ -11,6 +11,7 @@ from supervisor.bootstrap import initialize_coresys
from supervisor.coresys import CoreSys from supervisor.coresys import CoreSys
from supervisor.dbus.const import DBUS_NAME_NM, DBUS_OBJECT_BASE from supervisor.dbus.const import DBUS_NAME_NM, DBUS_OBJECT_BASE
from supervisor.dbus.network import NetworkManager from supervisor.dbus.network import NetworkManager
from supervisor.dbus.network.interface import NetworkInterface
from supervisor.docker import DockerAPI from supervisor.docker import DockerAPI
from supervisor.utils.gdbus import DBus from supervisor.utils.gdbus import DBus
@ -124,3 +125,12 @@ async def api_client(aiohttp_client, coresys):
api.webapp = web.Application() api.webapp = web.Application()
await api.load() await api.load()
yield await aiohttp_client(api.webapp) yield await aiohttp_client(api.webapp)
@pytest.fixture
async def network_interface(dbus):
"""Fixture for a network interface."""
interface = NetworkInterface()
await interface.connect(dbus, "/org/freedesktop/NetworkManager/ActiveConnection/1")
await interface.connection.update_information()
yield interface

View File

@ -0,0 +1,49 @@
"""Test interface update payload."""
import pytest
from supervisor.dbus.payloads.interface_update import interface_update_payload
from supervisor.utils.gdbus import DBus
@pytest.mark.asyncio
async def test_interface_update_payload(network_interface):
"""Test interface update payload."""
assert (
interface_update_payload(network_interface, **{"method": "auto"})
== """{
'connection':
{
'id': <'Wired connection 1'>,
'type': <'802-3-ethernet'>
},
'ipv4':
{
'method': <'auto'>
}
}"""
)
assert (
interface_update_payload(network_interface, **{})
== """{
'connection':
{
'id': <'Wired connection 1'>,
'type': <'802-3-ethernet'>
},
'ipv4':
{
'method': <'manual'>,
'dns': <[uint32 16951488]>,
'address-data': <[
{
'address': <'192.168.2.148'>,
'prefix': <uint32 24>
}]>,
'gateway': <'192.168.2.1'>
}
}"""
)
data = interface_update_payload(network_interface, **{"address": "1.1.1.1"})
assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "1.1.1.1"