Skip to content
Snippets Groups Projects
Commit 1cd86adb authored by Sybren A. Stüvel's avatar Sybren A. Stüvel
Browse files

Use UPnP/SSDP to automatically find Manager when manager_url is empty.

This is now also the new default, since we can't provide a sane default URL
anyway.
parent 2ffff180
No related branches found
No related tags found
No related merge requests found
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
This file logs the changes that are actually interesting to users (new features, This file logs the changes that are actually interesting to users (new features,
changed functionality, fixed bugs). changed functionality, fixed bugs).
## Version 2.0.7 (in development)
- Use UPnP/SSDP to automatically find Manager when manager_url is empty.
This is now also the new default, since we can't provide a sane default URL anyway.
## Version 2.0.6 (released 2017-06-23) ## Version 2.0.6 (released 2017-06-23)
- Fixed incompatibility with attrs version 17.1+. - Fixed incompatibility with attrs version 17.1+.
......
...@@ -34,7 +34,8 @@ The configuration files should be in INI format, as specified by the ...@@ -34,7 +34,8 @@ The configuration files should be in INI format, as specified by the
All configuration keys should be placed in the `[flamenco-worker]` section of the All configuration keys should be placed in the `[flamenco-worker]` section of the
config files. At least take a look at: config files. At least take a look at:
- `manager_url`: Flamenco Manager URL. - `manager_url`: Flamenco Manager URL. Leave blank to auto-discover Flamenco Manager
on your network using UPnP/SSDP.
- `task_types`: Space-separated list of task types this worker may execute. - `task_types`: Space-separated list of task types this worker may execute.
- `task_update_queue_db`: filename of the SQLite3 database holding the queue of task - `task_update_queue_db`: filename of the SQLite3 database holding the queue of task
updates to be sent to the Master. updates to be sent to the Master.
......
[flamenco-worker] [flamenco-worker]
manager_url = http://localhost:8083/
# The URL of the Flamenco Manager. Leave empty for auto-discovery via UPnP/SSDP.
manager_url =
task_types = sleep blender-render file-management task_types = sleep blender-render file-management
task_update_queue_db = flamenco-worker.db task_update_queue_db = flamenco-worker.db
......
...@@ -35,6 +35,19 @@ def main(): ...@@ -35,6 +35,19 @@ def main():
confparser.erase('worker_id') confparser.erase('worker_id')
confparser.erase('worker_secret') confparser.erase('worker_secret')
# Find the Manager using UPnP/SSDP if we have no manager_url.
if not confparser.value('manager_url'):
from . import ssdp_discover
try:
manager_url = ssdp_discover.find_flamenco_manager()
except ssdp_discover.DiscoveryFailed:
log.fatal('Unable to find Flamenco Manager via UPnP/SSDP.')
raise SystemExit(1)
log.info('Found Flamenco Manager at %s', manager_url)
confparser.setvalue('manager_url', manager_url)
# Patch AsyncIO # Patch AsyncIO
from . import patch_asyncio from . import patch_asyncio
patch_asyncio.patch_asyncio() patch_asyncio.patch_asyncio()
......
...@@ -14,7 +14,7 @@ CONFIG_SECTION = 'flamenco-worker' ...@@ -14,7 +14,7 @@ CONFIG_SECTION = 'flamenco-worker'
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
'flamenco-worker': collections.OrderedDict([ 'flamenco-worker': collections.OrderedDict([
('manager_url', 'http://flamenco-manager/'), ('manager_url', ''),
('task_types', 'unknown sleep blender-render'), ('task_types', 'unknown sleep blender-render'),
('task_update_queue_db', 'flamenco-worker.db'), ('task_update_queue_db', 'flamenco-worker.db'),
('may_i_run_interval_seconds', '5'), ('may_i_run_interval_seconds', '5'),
...@@ -39,6 +39,9 @@ class ConfigParser(configparser.ConfigParser): ...@@ -39,6 +39,9 @@ class ConfigParser(configparser.ConfigParser):
def value(self, key, valtype: type=str): def value(self, key, valtype: type=str):
return valtype(self.get(CONFIG_SECTION, key)) return valtype(self.get(CONFIG_SECTION, key))
def setvalue(self, key, value):
self.set(CONFIG_SECTION, key, value)
def interval_secs(self, key) -> datetime.timedelta: def interval_secs(self, key) -> datetime.timedelta:
"""Returns the configuration value as timedelta.""" """Returns the configuration value as timedelta."""
......
import logging
import socket
from http.client import HTTPResponse
DISCOVERY_MSG = (b'M-SEARCH * HTTP/1.1\r\n' +
b'ST: urn:flamenco:manager:0\r\n' +
b'MX: 3\r\n' +
b'MAN: "ssdp:discover"\r\n' +
b'HOST: 239.255.255.250:1900\r\n\r\n')
# We use site-local multicast, both in IPv6 and IPv4.
DESTINATIONS = {
socket.AF_INET6: 'FF05::C',
socket.AF_INET: '239.255.255.250',
}
log = logging.getLogger(__name__)
class DiscoveryFailed(Exception):
"""Raised when we cannot find a Manager through SSDP."""
class Response(HTTPResponse):
# noinspection PyMissingConstructor
def __init__(self, payload: bytes):
from io import BytesIO
self.fp = BytesIO(payload)
self.debuglevel = 0
self.strict = 0
self.headers = self.msg = None
self._method = None
self.begin()
def interface_addresses():
for dest in ('0.0.0.0', '::'):
for family, _, _, _, sockaddr in socket.getaddrinfo(dest, None):
yield family, sockaddr[0]
def unique(addresses):
seen = set()
for family_addr in addresses:
if family_addr in seen:
continue
seen.add(family_addr)
yield family_addr
def find_flamenco_manager(timeout=1, retries=5):
log.info('Finding Flamenco Manager through UPnP/SSDP discovery.')
socket.setdefaulttimeout(timeout)
families_and_addresses = list(unique(interface_addresses()))
for _ in range(retries):
for family, addr in families_and_addresses:
try:
dest = DESTINATIONS[family]
except KeyError:
log.warning('Unknown address family %s, skipping', family)
continue
log.debug('Sending to %s %s' % (family, addr))
sock = socket.socket(family, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.bind((addr, 0))
for _ in range(2):
# sending it more than once will
# decrease the probability of a timeout
sock.sendto(DISCOVERY_MSG, (dest, 1900))
try:
data = sock.recv(1024)
except socket.timeout:
pass
else:
response = Response(data)
return response.getheader('Location')
raise DiscoveryFailed('Unable to find Flamenco Manager after %i tries' % retries)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
location = find_flamenco_manager()
print('Found the service at %s' % location)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment