Download

Documentation

Community

Development

Ticket #8: autodiscovery2

File autodiscovery2, 6.6 KB (added by redduck666, 5 months ago)
Line 
1diff --git a/generate-cert.sh b/generate-cert.sh
2index 4daf76a..ce28e03 100755
3--- a/generate-cert.sh
4+++ b/generate-cert.sh
5@@ -5,4 +5,5 @@ if [ ! -x /usr/bin/certtool ] ; then
6 fi
7 
8 certtool --generate-privkey --outfile ca-key.pem
9-certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca-cert.pem
10\ No newline at end of file
11+certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca-cert.pem
12+
13diff --git a/manage.py b/manage.py
14old mode 100644
15new mode 100755
16diff --git a/pydra_server/cluster/master.py b/pydra_server/cluster/master.py
17index 47bb2a6..fe5d612 100755
18--- a/pydra_server/cluster/master.py
19+++ b/pydra_server/cluster/master.py
20@@ -46,6 +46,12 @@ import datetime
21 
22 from threading import Lock
23 
24+# should be executed before any other reactor stuff to prevent from using non
25+# glib2 event loop which we need for dbus
26+
27+from twisted.internet import glib2reactor
28+glib2reactor.install()
29+
30 from zope.interface import implements
31 from twisted.cred import portal, checkers
32 from twisted.spread import pb
33@@ -56,6 +62,8 @@ from twisted.web import server, resource
34 from twisted.cred import credentials
35 from django.utils import simplejson
36 import settings
37+import dbus, avahi
38+from dbus.mainloop.glib import DBusGMainLoop
39 
40 from pydra_server.models import Node, TaskInstance
41 from pydra_server.cluster.constants import *
42@@ -143,6 +151,66 @@ class Master(object):
43 
44         self.host = 'localhost'
45         self.port = 18800
46+        self.autodiscovery()
47+
48+    def autodiscovery(self, callback=None):
49+        """
50+        set up the dbus loop, and add the callbacks for adding nodes on the fly
51+
52+        based on http://avahi.org/wiki/PythonBrowseExample
53+        """
54+        def service_resolved(*args):
55+            # at this point we have all the info about the node we need
56+
57+            #decode TXT record, dbus's array of array's of dbus.Byte
58+            txt = ""
59+            for c in args[9][0]:
60+                if c >= 32 and c < 127:
61+                    txt += "%c" % c
62+                else:
63+                    txt += "."
64+
65+            # make sure we only accept nodes we haven't seen before this is NOT
66+            # a security measure (the key verification still takes place), only
67+            # intended to prevent multiple hosts from being discovered via
68+            # different routes (for example over both ipv6 and ipv4)
69+            from md5 import md5
70+            for node in self.nodes.values():
71+                if md5(str(node.pub_key)).hexdigest() == txt:
72+                    return
73+
74+            # add the node (without the restart)
75+            node = Node.objects.create(host=args[7], port=args[8])
76+            self.nodes[node.id] = node
77+            self.reconnect_nodes()
78+
79+        def print_error(*args):
80+            logger.info("Couldn't resolve avahi name: %s" % str(args))
81+
82+        def node_found(interface, protocol, name, stype, domain, flags):
83+            if flags & avahi.LOOKUP_RESULT_LOCAL:
84+                    # local service, skip
85+                    pass
86+
87+            server.ResolveService(interface, protocol, name, stype,
88+                domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
89+                reply_handler=service_resolved, error_handler=print_error)
90+
91+        # initialize dbus stuff needed for discovery
92+        loop = DBusGMainLoop()
93+
94+        bus = dbus.SystemBus(mainloop=loop)
95+
96+        server = dbus.Interface( bus.get_object(avahi.DBUS_NAME, '/'),
97+                'org.freedesktop.Avahi.Server')
98+
99+        sbrowser = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
100+                server.ServiceBrowserNew(avahi.IF_UNSPEC,
101+                    avahi.PROTO_UNSPEC, '_pydra._tcp', 'local', dbus.UInt32(0))),
102+                avahi.DBUS_INTERFACE_SERVICE_BROWSER)
103+
104+        sbrowser.connect_to_signal("ItemNew", node_found)
105+
106 
107     def get_services(self):
108         """
109diff --git a/pydra_server/cluster/node.py b/pydra_server/cluster/node.py
110index d4a727f..4eb9ce0 100755
111--- a/pydra_server/cluster/node.py
112+++ b/pydra_server/cluster/node.py
113@@ -46,6 +46,8 @@ from twisted.application import service, internet
114 
115 import os
116 from subprocess import Popen
117+import platform, dbus, avahi
118+from django.utils import simplejson
119 from pydra_server.cluster.auth.rsa_auth import load_crypto
120 from pydra_server.cluster.auth.master_avatar import MasterAvatar
121 
122@@ -55,6 +57,46 @@ import settings
123 from pydra_server.logging.logger import init_logging
124 logger = init_logging(settings.LOG_FILENAME_NODE)
125 
126+class ZeroconfService:
127+    """A simple class to publish a network service with zeroconf using
128+    avahi.
129+
130+    Shamelessly stolen from http://stackp.online.fr/?p=35
131+    """
132+
133+    def __init__(self, name, port, stype="_http._tcp",
134+                 domain="", host="", text=""):
135+        self.name = name
136+        self.stype = stype
137+        self.domain = domain
138+        self.host = host
139+        self.port = port
140+        self.text = text
141+
142+    def publish(self):
143+        bus = dbus.SystemBus()
144+        server = dbus.Interface(
145+                         bus.get_object(
146+                                 avahi.DBUS_NAME,
147+                                 avahi.DBUS_PATH_SERVER),
148+                        avahi.DBUS_INTERFACE_SERVER)
149+
150+        g = dbus.Interface(
151+                    bus.get_object(avahi.DBUS_NAME,
152+                                   server.EntryGroupNew()),
153+                    avahi.DBUS_INTERFACE_ENTRY_GROUP)
154+
155+        g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,dbus.UInt32(0),
156+                     self.name, self.stype, self.domain, self.host,
157+                     dbus.UInt16(self.port), self.text)
158+
159+        g.Commit()
160+        #import ipdb; ipdb.set_trace()
161+        self.group = g
162+
163+    def unpublish(self):
164+        self.group.Reset()
165+
166 
167 class NodeServer:
168     """
169@@ -81,6 +123,18 @@ class NodeServer:
170         # get information about the server
171         self.determine_info()
172 
173+
174+        # TXT record must be array of array of dbus.Byte, let's encode it
175+        dbus_array = []
176+        from md5 import md5
177+
178+        for c in md5(simplejson.dumps(self.pub_key)).hexdigest():
179+            dbus_array.append(dbus.Byte(ord(c)))
180+        service = ZeroconfService(name=platform.node(), port=self.port,
181+            stype="_pydra._tcp", text=[dbus_array])
182+        service.publish()
183+
184+
185         logger.info('Node - starting server on port %s' % self.port)
186 
187 
188@@ -142,7 +196,6 @@ class NodeServer:
189         Exchange public keys with the master.  This allows the Master
190         to authenticate in the future using the keypair handshake
191         """
192-        from django.utils import simplejson
193         from Crypto.PublicKey import RSA
194         from twisted.conch.ssh.keys import Key
195         import math