gstlal
1.13.0
Toggle main menu visibility
Loading...
Searching...
No Matches
python
servicediscovery.py
1
# Copyright (C) 2012--2014,2016--2018 Kipp Cannon
2
#
3
# This program is free software; you can redistribute it and/or modify it
4
# under the terms of the GNU General Public License as published by the
5
# Free Software Foundation; either version 2 of the License, or (at your
6
# option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11
# Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License along
14
# with this program; if not, write to the Free Software Foundation, Inc.,
15
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17
18
#
19
# =============================================================================
20
#
21
# Preamble
22
#
23
# =============================================================================
24
#
25
26
27
import
avahi
28
29
30
from
gi.repository
import
Gio
31
32
33
__all__ = [
"DEFAULT_SERVICE_TYPE"
,
"DEFAULT_SERVICE_DOMAIN"
,
"Publisher"
,
"Listener"
,
"ServiceBrowser"
]
34
35
36
__author__ =
"Kipp Cannon <kipp.cannon@ligo.org>"
37
__version__ =
"FIXME"
38
__date__ =
"FIXME"
39
40
41
#
42
# =============================================================================
43
#
44
# HTTP Service Metadata
45
#
46
# =============================================================================
47
#
48
49
50
DEFAULT_SERVICE_TYPE =
"_http._tcp"
51
DEFAULT_SERVICE_DOMAIN =
"gw.local"
52
53
54
#
55
# =============================================================================
56
#
57
# Service Publishing
58
#
59
# =============================================================================
60
#
61
62
63
class
Service
(object):
64
"""
65
Add a service to a group, and allow its properties to be updated
66
later.
67
"""
68
@staticmethod
69
def
properties_to_txt_array(properties):
70
if
properties
is
None
:
71
properties = {}
72
elif
any(
"="
in
key
for
key
in
properties):
73
raise
ValueError(
"'=' not permitted in property keys"
)
74
return
avahi.dict_to_txt_array(properties)
75
76
77
def
__init__
(self, group, sname, port, stype = None, sdomain = None, host = None, properties = None):
78
"""
79
Add a service to the collection of services currently
80
advertised. sname and port specify the service name and
81
the port number on which the service can be found. stype
82
and sdomain set the service type and service domain; if
83
not set the module-level symbols DEFAULT_SERVICE_TYPE and
84
DEFAULT_SERVICE_DOMAIN are used, respectively.
85
86
Avahi is asked to advertise the service on all network
87
interfaces to which it is connected. If host is "" (the
88
default) then on each interface avahi will use the host
89
name corresponding to that network interface (as determined
90
by itself). This is a convenient way to ensure the service
91
is advertised on each interface with a host name that
92
exists on that interface's network.
93
94
properties is a dictionary of name-value pairs all of which
95
are strings. "=" is not allowed in any of the names.
96
"""
97
#
98
# this information will be needed to make updates
99
#
100
101
self.
group
= group
102
self.
sname
= sname
103
self.
stype
= stype
if
stype
is
not
None
else
DEFAULT_SERVICE_TYPE
104
self.
sdomain
= sdomain
if
sdomain
is
not
None
else
DEFAULT_SERVICE_DOMAIN
105
if
self.
sdomain
.split(
"."
)[-1] !=
"local"
:
106
raise
ValueError(
"sdomain must end in 'local': %s"
% self.
sdomain
)
107
108
#
109
# add the service to the avahi service group
110
#
111
112
group.AddService(
113
"(iiussssqaay)"
,
114
avahi.IF_UNSPEC,
# interface
115
avahi.PROTO_INET,
# protocol
116
0,
# flags
117
sname,
# service name
118
self.
stype
,
# service type
119
self.
sdomain
,
# service domain
120
host
if
host
is
not
None
else
""
,
# host name
121
port,
# port
122
self.
properties_to_txt_array
(properties)
# text/description
123
)
124
125
126
def
set_properties
(self, properties = None):
127
"""
128
properties is a dictionary of name-value pairs all of which
129
are strings. "=" is not allowed in any of the names.
130
"""
131
self.
group
.UpdateServiceTxt(
132
"(iiusssaay)"
,
133
avahi.IF_UNSPEC,
# interface
134
avahi.PROTO_INET,
# protocol
135
0,
# flags
136
self.
sname
,
# service name
137
self.
stype
,
# service type
138
self.
sdomain
,
# service domain
139
self.
properties_to_txt_array
(properties)
# text/description
140
)
141
142
143
class
Publisher
(object):
144
"""
145
Glue code to connect to the avahi daemon through dbus and manage
146
the advertisement of services.
147
"""
148
def
__enter__(self):
149
bus = Gio.bus_get_sync(Gio.BusType.SYSTEM,
None
)
150
server = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE,
None
, avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER, avahi.DBUS_INTERFACE_SERVER,
None
)
151
group_path = server.EntryGroupNew(
"()"
)
152
self.
group
= Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE,
None
, avahi.DBUS_NAME, group_path, avahi.DBUS_INTERFACE_ENTRY_GROUP,
None
)
153
return
self
154
155
def
add_service
(self, sname, port, stype = None, sdomain = None, host = None, properties = None, commit = True):
156
"""
157
See the Service class for the meaning of the arguments.
158
159
If commit is True (the default), then the new service is
160
advertised immediately along with all other previously
161
unadvertised services; otherwise the calling code is
162
responsible for calling the .commit() method itself.
163
"""
164
service =
Service
(self.
group
, sname, port, stype, sdomain, host, properties)
165
if
commit:
166
self.
commit
()
167
return
service
168
169
def
commit(self):
170
self.
group
.Commit(
"()"
)
171
172
def
__exit__
(self, exc_type, exc_value, traceback):
173
"""
174
Unpublish all services.
175
"""
176
self.
group
.Reset(
"()"
)
177
178
179
#
180
# =============================================================================
181
#
182
# Service Discovery
183
#
184
# =============================================================================
185
#
186
187
188
class
Listener
(object):
189
"""
190
Parent class for Listener implementations. Each method corresponds
191
to an event type. Subclasses override the desired methods with the
192
code to be invoked upon those events. The default methods are all
193
no-ops. An instance of a Listener implementation is required to
194
initialize a ServiceBrowser.
195
"""
196
def
add_service(self, sname, stype, sdomain, host, port, properties):
197
pass
198
199
def
remove_service(self, sname, stype, sdomain):
200
pass
201
202
def
all_for_now(self):
203
pass
204
205
def
failure(self, *args):
206
pass
207
208
209
class
ServiceBrowser(object):
210
"""
211
Glue code to connect a Listener implementation to the avahi daemon
212
through dbus.
213
"""
214
def
__init__
(self, listener, stype = DEFAULT_SERVICE_TYPE, sdomain = DEFAULT_SERVICE_DOMAIN, ignore_local = False):
215
"""
216
Connects to the avahi daemon through dbus, requests an
217
avahi ServiceBrowser instance from the daemon configured to
218
browse for the given service type and domain, then connects
219
signal handlers that forward information from avahi to the
220
methods of a Listener instance.
221
222
listener is an instance of a subclass of Listener (or any
223
other object that provides the required methods to be used
224
as call-backs).
225
226
if ignore_local is True then services discovered on the
227
local machine itself will be ignored (the default is False,
228
all discovered services are reported to the Listener).
229
"""
230
self.
listener
= listener
231
self.
ignore_local
= ignore_local
232
bus = Gio.bus_get_sync(Gio.BusType.SYSTEM,
None
)
233
self.
server
= Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE,
None
, avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER, avahi.DBUS_INTERFACE_SERVER,
None
)
234
browser_path = self.
server
.ServiceBrowserNew(
235
"(iissu)"
,
236
avahi.IF_UNSPEC,
# interface
237
avahi.PROTO_UNSPEC,
# protocol
238
stype,
# service type
239
sdomain,
# service domain
240
0
# flags
241
)
242
bus.signal_subscribe(
None
,
None
,
"ItemNew"
, browser_path,
None
, Gio.DBusSignalFlags.NONE, self.
itemnew_handler
,
None
)
243
bus.signal_subscribe(
None
,
None
,
"ItemRemove"
, browser_path,
None
, Gio.DBusSignalFlags.NONE, self.
itemremove_handler
,
None
)
244
bus.signal_subscribe(
None
,
None
,
"AllForNow"
, browser_path,
None
, Gio.DBusSignalFlags.NONE, self.
allfornow_handler
,
None
)
245
bus.signal_subscribe(
None
,
None
,
"Failure"
, browser_path,
None
, Gio.DBusSignalFlags.NONE, self.
failure_handler
,
None
)
246
247
def
itemnew_handler(self, bus, sender_name, object_path, interface_name, signal_name, service_config, data):
248
"""
249
Internal ItemNew signal handler. Forwards the essential
250
information to the Listener's .add_service() method.
251
"""
252
interface, protocol, sname, stype, sdomain, flags = service_config
253
if
self.
ignore_local
and
(flags & avahi.LOOKUP_RESULT_LOCAL):
254
# local service (on this machine)
255
return
256
interface, protocol, sname, stype, sdomain, host, aprotocol, address, port, txt, flags = self.
server
.ResolveService(
257
"(iisssiu)"
,
258
interface,
259
protocol,
260
sname,
261
stype,
262
sdomain,
263
avahi.PROTO_UNSPEC,
264
0
265
)
266
self.
listener
.add_service(sname, stype, sdomain, host, port, dict(s.split(
"="
, 1)
for
s
in
avahi.txt_array_to_string_array(txt)))
267
268
def
itemremove_handler(self, bus, sender_name, object_path, interface_name, signal_name, service_config, data):
269
"""
270
Internal ItemRemove signal handler. Forwards the essential
271
information to the Listener's .remove_service() method.
272
"""
273
interface, protocol, sname, stype, sdomain, flags = service_config
274
if
self.
ignore_local
and
(flags & avahi.LOOKUP_RESULT_LOCAL):
275
# local service (on this machine)
276
return
277
self.
listener
.remove_service(sname, stype, sdomain)
278
279
def
allfornow_handler(self, bus, sender_name, object_path, interface_name, signal_name, parameters, data):
280
"""
281
Internal AllForNow signal handler. Forwards the essential
282
information to the Listener's .all_for_now() method.
283
"""
284
self.
listener
.all_for_now()
285
286
def
failure_handler(self, bus, sender_name, object_path, interface_name, signal_name, parameters, data):
287
"""
288
Internal Failure signal handler. Forwards the essential
289
information to the Listener's .failure() method.
290
"""
291
self.
listener
.failure(*parameters)
292
293
294
#
295
# =============================================================================
296
#
297
# Demo
298
#
299
# =============================================================================
300
#
301
302
303
if
__name__ ==
"__main__"
:
304
#
305
# usage:
306
#
307
# python /path/to/servicediscovery.py [publish]
308
#
309
# if publish is given on the command line then a service is
310
# published, otherwise a browser is started and discovered services
311
# are printed
312
#
313
314
from
gi.repository
import
GLib
315
import
sys
316
317
if
sys.argv[-1] ==
"publish"
:
318
#
319
# publish a service
320
#
321
322
with
Publisher
()
as
publisher:
323
publisher.add_service(
324
sname =
"My Test Service"
,
325
port = 3456,
326
properties = {
327
"version"
:
"0.10"
,
328
"a"
:
"test value"
,
329
"b"
:
"another value"
330
}
331
)
332
raw_input(
"Service published. Press return to unpublish and quit.\n"
)
333
else
:
334
#
335
# browse for services
336
#
337
338
class
MyListener
(
Listener
):
339
def
print_msg(self, action, sname, stype, sdomain, host, port, properties):
340
print(
"Service \"%s\" %s"
% (sname, action), file=sys.stderr)
341
print(
"\tType is \"%s\""
% stype, file=sys.stderr)
342
print(
"\tDomain is \"%s\""
% sdomain, file=sys.stderr)
343
print(
"\tHost is \"%s\""
% host, file=sys.stderr)
344
print(
"\tPort is %s"
% port, file=sys.stderr)
345
print(
"\tProperties are %s\n"
% properties, file=sys.stderr)
346
def
add_service(self, sname, stype, sdomain, host, port, properties):
347
self.
print_msg
(
"added"
, sname, stype, sdomain, host, port, properties)
348
def
remove_service(self, sname, stype, sdomain):
349
self.
print_msg
(
"removed"
, sname, stype, sdomain,
None
,
None
,
None
)
350
351
def
all_for_now(self):
352
print(
"All for now\n"
, file=sys.stderr)
353
354
def
failure(self, *args):
355
print(
"failure"
, args, file=sys.stderr)
356
mainloop = GLib.MainLoop()
357
browser =
ServiceBrowser
(
MyListener
())
358
print(
"Browsing for services. Press CTRL-C to quit.\n"
)
359
mainloop.run()
python.servicediscovery.Listener
Definition
servicediscovery.py:188
python.servicediscovery.MyListener
Definition
servicediscovery.py:338
python.servicediscovery.MyListener.print_msg
print_msg(self, action, sname, stype, sdomain, host, port, properties)
Definition
servicediscovery.py:339
python.servicediscovery.Publisher
Definition
servicediscovery.py:143
python.servicediscovery.Publisher.commit
commit(self)
Definition
servicediscovery.py:169
python.servicediscovery.Publisher.group
group
Definition
servicediscovery.py:152
python.servicediscovery.Publisher.__exit__
__exit__(self, exc_type, exc_value, traceback)
Definition
servicediscovery.py:172
python.servicediscovery.Publisher.add_service
add_service(self, sname, port, stype=None, sdomain=None, host=None, properties=None, commit=True)
Definition
servicediscovery.py:155
python.servicediscovery.ServiceBrowser
Definition
servicediscovery.py:209
python.servicediscovery.ServiceBrowser.allfornow_handler
allfornow_handler
Definition
servicediscovery.py:244
python.servicediscovery.ServiceBrowser.listener
listener
Definition
servicediscovery.py:230
python.servicediscovery.ServiceBrowser.failure_handler
failure_handler
Definition
servicediscovery.py:245
python.servicediscovery.ServiceBrowser.itemremove_handler
itemremove_handler
Definition
servicediscovery.py:243
python.servicediscovery.ServiceBrowser.server
server
Definition
servicediscovery.py:233
python.servicediscovery.ServiceBrowser.__init__
__init__(self, listener, stype=DEFAULT_SERVICE_TYPE, sdomain=DEFAULT_SERVICE_DOMAIN, ignore_local=False)
Definition
servicediscovery.py:214
python.servicediscovery.ServiceBrowser.ignore_local
ignore_local
Definition
servicediscovery.py:231
python.servicediscovery.ServiceBrowser.itemnew_handler
itemnew_handler
Definition
servicediscovery.py:242
python.servicediscovery.Service
Definition
servicediscovery.py:63
python.servicediscovery.Service.set_properties
set_properties(self, properties=None)
Definition
servicediscovery.py:126
python.servicediscovery.Service.sname
sname
Definition
servicediscovery.py:102
python.servicediscovery.Service.stype
stype
Definition
servicediscovery.py:103
python.servicediscovery.Service.sdomain
sdomain
Definition
servicediscovery.py:104
python.servicediscovery.Service.group
group
Definition
servicediscovery.py:101
python.servicediscovery.Service.properties_to_txt_array
properties_to_txt_array(properties)
Definition
servicediscovery.py:69
python.servicediscovery.Service.__init__
__init__(self, group, sname, port, stype=None, sdomain=None, host=None, properties=None)
Definition
servicediscovery.py:77
Generated by
1.17.0