Skip to content

Commit

Permalink
Split Avahi stuff into its own file.
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxColoring committed May 26, 2022
1 parent a1ee676 commit 9444d84
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 109 deletions.
111 changes: 2 additions & 109 deletions update-server/otupdate/common/name_management/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@
import json
import logging
import os
from typing import Optional
import urllib.parse

from aiohttp import web

from ..constants import DEVICE_NAME_VARNAME

from .avahi import set_avahi_service_name
from .pretty_hostname import (
get_pretty_hostname,
persist_pretty_hostname,
Expand All @@ -59,114 +60,6 @@
LOG = logging.getLogger(__name__)


try:
import dbus

class DBusState:
"""Bundle of state for dbus"""

def __init__(
self,
bus: dbus.SystemBus,
server: dbus.Interface,
entrygroup: dbus.Interface,
) -> None:
"""
Build the state bundle.
:param bus: The system bus instance
:param server: An org.freedesktop.Avahi.Server interface
:param entrygroup: An org.freedesktop.Avahi.EntryGroup interface
"""
self.bus = bus
#: The system bus
self.entrygroup = entrygroup
#: The entry group interface
self.server = server
#: The avahi server interface

_BUS_STATE: Optional[DBusState] = None

def _set_avahi_service_name_sync(new_service_name: str) -> None:
"""The synchronous implementation of setting the Avahi service name.
The dbus module doesn't natively support async/await.
"""
# For semantics of the methods we're calling, see Avahi's API docs.
# For example: https://www.avahi.org/doxygen/html/index.html#good_publish
# It's mostly in terms of the C API, but the semantics should be the same.
#
# For exact method names and argument types, see Avahi's D-Bus bindings,
# which they specify across several machine-readable files. For example:
# https://github.com/lathiat/avahi/blob/v0.7/avahi-daemon/org.freedesktop.Avahi.EntryGroup.xml
global _BUS_STATE
if not _BUS_STATE:
bus = dbus.SystemBus()
server_obj = bus.get_object("org.freedesktop.Avahi", "/")
server_if = dbus.Interface(server_obj, "org.freedesktop.Avahi.Server")
entrygroup_path = server_if.EntryGroupNew()
entrygroup_obj = bus.get_object("org.freedesktop.Avahi", entrygroup_path)
entrygroup_if = dbus.Interface(
entrygroup_obj, "org.freedesktop.Avahi.EntryGroup"
)
_BUS_STATE = DBusState(bus, server_if, entrygroup_if)

_BUS_STATE.entrygroup.Reset()

hostname = _BUS_STATE.server.GetHostName()
domainname = _BUS_STATE.server.GetDomainName()

# TODO(mm, 2022-05-06): This isn't exception-safe.
# Since we've already reset the entrygroup, if this fails
# (for example because Avahi doesn't like the new name),
# we'll be left with no entrygroup and Avahi will stop advertising the machine.
_BUS_STATE.entrygroup.AddService(
dbus.Int32(-1), # avahi.IF_UNSPEC
dbus.Int32(-1), # avahi.PROTO_UNSPEC
dbus.UInt32(0), # flags
new_service_name, # sname
"_http._tcp", # stype
domainname, # sdomain (.local)
f"{hostname}.{domainname}", # shost (hostname.local)
dbus.UInt16(31950), # port
dbus.Array([], signature="ay"),
)
_BUS_STATE.entrygroup.Commit()

# TODO(mm, 2022-05-04): Recover from AVAHI_ENTRY_GROUP_COLLISION in case
# this name collides with another device on the network.
# https://github.com/Opentrons/opentrons/issues/10126

except ImportError:
LOG.exception("Couldn't import dbus, name setting will be nonfunctional")

def _set_avahi_service_name_sync(new_service_name: str) -> None:
LOG.warning("Not setting name, dbus could not be imported")


_BUS_LOCK = asyncio.Lock()


async def set_avahi_service_name(new_service_name: str) -> None:
"""Set the Avahi service name.
The new service name will only apply to the current boot.
Avahi requires a service name.
It will not advertise the system over mDNS + DNS-SD until one is set.
Since the Avahi service name corresponds to the DNS-SD instance name,
it's a human-readable string of mostly arbitrary Unicode,
at most 63 octets (not 63 code points or 63 characters!) long.
(See: https://datatracker.ietf.org/doc/html/rfc6763#section-4.1.1)
Avahi will raise an error if it thinks the new service name is invalid.
"""
async with _BUS_LOCK:
await asyncio.get_event_loop().run_in_executor(
None, _set_avahi_service_name_sync, new_service_name
)


def _choose_static_hostname() -> str:
"""Get a good value for the system's static hostname.
Expand Down
114 changes: 114 additions & 0 deletions update-server/otupdate/common/name_management/avahi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import asyncio
from logging import getLogger
from typing import Optional


LOG = getLogger(__name__)


try:
import dbus

class DBusState:
"""Bundle of state for dbus"""

def __init__(
self,
bus: dbus.SystemBus,
server: dbus.Interface,
entrygroup: dbus.Interface,
) -> None:
"""
Build the state bundle.
:param bus: The system bus instance
:param server: An org.freedesktop.Avahi.Server interface
:param entrygroup: An org.freedesktop.Avahi.EntryGroup interface
"""
self.bus = bus
#: The system bus
self.entrygroup = entrygroup
#: The entry group interface
self.server = server
#: The avahi server interface

_BUS_STATE: Optional[DBusState] = None

def _set_avahi_service_name_sync(new_service_name: str) -> None:
"""The synchronous implementation of setting the Avahi service name.
The dbus module doesn't natively support async/await.
"""
# For semantics of the methods we're calling, see Avahi's API docs.
# For example: https://www.avahi.org/doxygen/html/index.html#good_publish
# It's mostly in terms of the C API, but the semantics should be the same.
#
# For exact method names and argument types, see Avahi's D-Bus bindings,
# which they specify across several machine-readable files. For example:
# https://github.com/lathiat/avahi/blob/v0.7/avahi-daemon/org.freedesktop.Avahi.EntryGroup.xml
global _BUS_STATE
if not _BUS_STATE:
bus = dbus.SystemBus()
server_obj = bus.get_object("org.freedesktop.Avahi", "/")
server_if = dbus.Interface(server_obj, "org.freedesktop.Avahi.Server")
entrygroup_path = server_if.EntryGroupNew()
entrygroup_obj = bus.get_object("org.freedesktop.Avahi", entrygroup_path)
entrygroup_if = dbus.Interface(
entrygroup_obj, "org.freedesktop.Avahi.EntryGroup"
)
_BUS_STATE = DBusState(bus, server_if, entrygroup_if)

_BUS_STATE.entrygroup.Reset()

hostname = _BUS_STATE.server.GetHostName()
domainname = _BUS_STATE.server.GetDomainName()

# TODO(mm, 2022-05-06): This isn't exception-safe.
# Since we've already reset the entrygroup, if this fails
# (for example because Avahi doesn't like the new name),
# we'll be left with no entrygroup and Avahi will stop advertising the machine.
_BUS_STATE.entrygroup.AddService(
dbus.Int32(-1), # avahi.IF_UNSPEC
dbus.Int32(-1), # avahi.PROTO_UNSPEC
dbus.UInt32(0), # flags
new_service_name, # sname
"_http._tcp", # stype
domainname, # sdomain (.local)
f"{hostname}.{domainname}", # shost (hostname.local)
dbus.UInt16(31950), # port
dbus.Array([], signature="ay"),
)
_BUS_STATE.entrygroup.Commit()

# TODO(mm, 2022-05-04): Recover from AVAHI_ENTRY_GROUP_COLLISION in case
# this name collides with another device on the network.
# https://github.com/Opentrons/opentrons/issues/10126

except ImportError:
LOG.exception("Couldn't import dbus, name setting will be nonfunctional")

def _set_avahi_service_name_sync(new_service_name: str) -> None:
LOG.warning("Not setting name, dbus could not be imported")


_BUS_LOCK = asyncio.Lock()


async def set_avahi_service_name(new_service_name: str) -> None:
"""Set the Avahi service name.
The new service name will only apply to the current boot.
Avahi requires a service name.
It will not advertise the system over mDNS + DNS-SD until one is set.
Since the Avahi service name corresponds to the DNS-SD instance name,
it's a human-readable string of mostly arbitrary Unicode,
at most 63 octets (not 63 code points or 63 characters!) long.
(See: https://datatracker.ietf.org/doc/html/rfc6763#section-4.1.1)
Avahi will raise an error if it thinks the new service name is invalid.
"""
async with _BUS_LOCK:
await asyncio.get_event_loop().run_in_executor(
None, _set_avahi_service_name_sync, new_service_name
)

0 comments on commit 9444d84

Please sign in to comment.