Download

Documentation

Community

Development

Ticket #8: autodiscovery3

File autodiscovery3, 12.9 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/amf/interface.py b/pydra_server/cluster/amf/interface.py
17index 70ce268..e7c278f 100644
18--- a/pydra_server/cluster/amf/interface.py
19+++ b/pydra_server/cluster/amf/interface.py
20@@ -211,6 +211,20 @@ class AMFInterface(pb.Root):
21         """
22         return self.master._running
23 
24+    @authenticated
25+    def list_known_nodes(self, _):
26+        """
27+        list know_nodes
28+        """
29+        # cast to list, doesn't seem to digest set
30+        return list(self.master.known_nodes)
31+
32+    @authenticated
33+    def reconnect_nodes(self, _):
34+        """
35+        allows the gui to make the master aware of the new node without restart
36+        """
37+        return self.master.reconnect_nodes()
38 
39     @authenticated
40     def run_task(self, _, task_key, args=None):
41diff --git a/pydra_server/cluster/master.py b/pydra_server/cluster/master.py
42index 47bb2a6..94d41e7 100755
43--- a/pydra_server/cluster/master.py
44+++ b/pydra_server/cluster/master.py
45@@ -46,6 +46,12 @@ import datetime
46 
47 from threading import Lock
48 
49+# should be executed before any other reactor stuff to prevent from using non
50+# glib2 event loop which we need for dbus
51+
52+from twisted.internet import glib2reactor
53+glib2reactor.install()
54+
55 from zope.interface import implements
56 from twisted.cred import portal, checkers
57 from twisted.spread import pb
58@@ -56,6 +62,8 @@ from twisted.web import server, resource
59 from twisted.cred import credentials
60 from django.utils import simplejson
61 import settings
62+import dbus, avahi
63+from dbus.mainloop.glib import DBusGMainLoop
64 
65 from pydra_server.models import Node, TaskInstance
66 from pydra_server.cluster.constants import *
67@@ -123,6 +131,7 @@ class Master(object):
68         #cluster management
69         self.workers = {}
70         self.nodes = self.load_nodes()
71+        self.known_nodes = set()
72         self._workers_idle = []
73         self._workers_working = {}
74 
75@@ -143,6 +152,54 @@ class Master(object):
76 
77         self.host = 'localhost'
78         self.port = 18800
79+        self.autodiscovery()
80+
81+    def autodiscovery(self, callback=None):
82+        """
83+        set up the dbus loop, and add the callbacks for adding nodes on the fly
84+
85+        based on http://avahi.org/wiki/PythonBrowseExample
86+        """
87+        from pydra_server.models import pydraSettings
88+
89+        def service_resolved(*args):
90+            # at this point we have all the info about the node we need
91+            if pydraSettings.multicast_all:
92+
93+                # add the node (without the restart)
94+                Node.objects.create(host=args[7], port=args[8])
95+                self.reconnect_nodes()
96+            else:
97+                self.known_nodes.add((args[7], args[8]))
98+
99+        def print_error(*args):
100+            logger.info("Couldn't resolve avahi name: %s" % str(args))
101+
102+        def node_found(interface, protocol, name, stype, domain, flags):
103+            if flags & avahi.LOOKUP_RESULT_LOCAL:
104+                    # local service, skip
105+                    pass
106+
107+            server.ResolveService(interface, protocol, name, stype,
108+                domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
109+                reply_handler=service_resolved, error_handler=print_error)
110+
111+
112+        # initialize dbus stuff needed for discovery
113+        loop = DBusGMainLoop()
114+
115+        bus = dbus.SystemBus(mainloop=loop)
116+
117+        server = dbus.Interface( bus.get_object(avahi.DBUS_NAME, '/'),
118+                'org.freedesktop.Avahi.Server')
119+
120+        sbrowser = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
121+                server.ServiceBrowserNew(avahi.IF_UNSPEC,
122+                    avahi.PROTO_UNSPEC, '_pydra._tcp', 'local', dbus.UInt32(0))),
123+                avahi.DBUS_INTERFACE_SERVICE_BROWSER)
124+
125+        sbrowser.connect_to_signal("ItemNew", node_found)
126+
127 
128     def get_services(self):
129         """
130@@ -298,6 +355,13 @@ class Master(object):
131             #it's possible that multiple nodes can have problems at the same time
132             #reset_counter overrides this
133             if not self.connecting or reset_counter:
134+                # make sure that all nodes in the database are used
135+                for i in Node.objects.all():
136+                    if i.id not in self.nodes:
137+                        self.nodes[i.id] = i
138+                    if (i.host, i.port) in self.known_nodes:
139+                        self.known_nodes.discard((i.host, i.port))
140+
141                 self.connecting = True
142 
143                 #reset the counter, useful when a new failure occurs
144diff --git a/pydra_server/cluster/node.py b/pydra_server/cluster/node.py
145index d4a727f..f619090 100755
146--- a/pydra_server/cluster/node.py
147+++ b/pydra_server/cluster/node.py
148@@ -46,6 +46,8 @@ from twisted.application import service, internet
149 
150 import os
151 from subprocess import Popen
152+import platform, dbus, avahi
153+from django.utils import simplejson
154 from pydra_server.cluster.auth.rsa_auth import load_crypto
155 from pydra_server.cluster.auth.master_avatar import MasterAvatar
156 
157@@ -55,6 +57,45 @@ import settings
158 from pydra_server.logging.logger import init_logging
159 logger = init_logging(settings.LOG_FILENAME_NODE)
160 
161+class ZeroconfService:
162+    """A simple class to publish a network service with zeroconf using
163+    avahi.
164+
165+    Shamelessly stolen from http://stackp.online.fr/?p=35
166+    """
167+
168+    def __init__(self, name, port, stype="_http._tcp",
169+                 domain="", host="", text=""):
170+        self.name = name
171+        self.stype = stype
172+        self.domain = domain
173+        self.host = host
174+        self.port = port
175+        self.text = text
176+
177+    def publish(self):
178+        bus = dbus.SystemBus()
179+        server = dbus.Interface(
180+                         bus.get_object(
181+                                 avahi.DBUS_NAME,
182+                                 avahi.DBUS_PATH_SERVER),
183+                        avahi.DBUS_INTERFACE_SERVER)
184+
185+        g = dbus.Interface(
186+                    bus.get_object(avahi.DBUS_NAME,
187+                                   server.EntryGroupNew()),
188+                    avahi.DBUS_INTERFACE_ENTRY_GROUP)
189+
190+        g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,dbus.UInt32(0),
191+                     self.name, self.stype, self.domain, self.host,
192+                     dbus.UInt16(self.port), self.text)
193+
194+        g.Commit()
195+        self.group = g
196+
197+    def unpublish(self):
198+        self.group.Reset()
199+
200 
201 class NodeServer:
202     """
203@@ -81,6 +122,10 @@ class NodeServer:
204         # get information about the server
205         self.determine_info()
206 
207+        service = ZeroconfService(name=platform.node(), port=self.port,
208+            stype="_pydra._tcp")
209+        service.publish()
210+
211         logger.info('Node - starting server on port %s' % self.port)
212 
213 
214@@ -142,7 +187,6 @@ class NodeServer:
215         Exchange public keys with the master.  This allows the Master
216         to authenticate in the future using the keypair handshake
217         """
218-        from django.utils import simplejson
219         from Crypto.PublicKey import RSA
220         from twisted.conch.ssh.keys import Key
221         import math
222diff --git a/pydra_server/models.py b/pydra_server/models.py
223index 36644f2..9dfc104 100644
224--- a/pydra_server/models.py
225+++ b/pydra_server/models.py
226@@ -29,8 +29,9 @@ Settings
227 from _mysql_exceptions import ProgrammingError
228 try:
229     class PydraSettings(dbsettings.Group):
230-        host        = dbsettings.StringValue('host', 'IP Address or hostname for this server.  This value will be used by all nodes in the cluster to connect', default='localhost')
231-        port        = dbsettings.IntegerValue('port','Port for this server', default=18800)
232+        host                = dbsettings.StringValue('host', 'IP Address or hostname for this server.  This value will be used by all nodes in the cluster to connect', default='localhost')
233+        port             = dbsettings.IntegerValue('port','Port for this server', default=18800)
234+        multicast_all    = dbsettings.BooleanValue('multicast_all', 'Wether to automatically use all the node\'s found', default=False)
235     pydraSettings = PydraSettings('Pydra')
236 
237 except ProgrammingError:
238diff --git a/pydra_server/templates/discover.html b/pydra_server/templates/discover.html
239index 6183247..657dae8 100644
240--- a/pydra_server/templates/discover.html
241+++ b/pydra_server/templates/discover.html
242@@ -1,4 +1,32 @@
243 {% extends "base.html" %}
244-{% block "content" %}
245-yay content
246+<!--
247+    Copyright 2009 Oregon State University
248+
249+    This file is part of Pydra.
250+
251+    Pydra is free software: you can redistribute it and/or modify
252+    it under the terms of the GNU General Public License as published by
253+    the Free Software Foundation, either version 3 of the License, or
254+    (at your option) any later version.
255+
256+    Pydra is distributed in the hope that it will be useful,
257+    but WITHOUT ANY WARRANTY; without even the implied warranty of
258+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
259+    GNU General Public License for more details.
260+
261+    You should have received a copy of the GNU General Public License
262+    along with Pydra.  If not, see <http://www.gnu.org/licenses/>.
263+-->
264+{% block content %}
265+{% if known_nodes %}
266+    Please put the checkbox next to the Node's you would like to see added and hit submit.
267+    <form method="post" action=".">
268+    {% for field in known_nodes %}
269+        <p>{{ field.0 }}:{{ field.1 }} <input type="checkbox" name="{{ field.0 }}:{{ field.1 }}"></p>
270+    {% endfor %}
271+    <input type="submit">
272+    </form>
273+{% else %}
274+    There appear to be no non active discovered nodes.
275+{% endif %}
276 {% endblock %}
277diff --git a/pydra_server/templates/header.html b/pydra_server/templates/header.html
278index 18c8c71..87249e7 100644
279--- a/pydra_server/templates/header.html
280+++ b/pydra_server/templates/header.html
281@@ -88,7 +88,7 @@
282     {% if nodes %}
283         <div id="submenu">
284             <span class="menuitem"><a class="menuitem" href="{{SITE_ROOT}}/nodes/edit">Create Node</a></span>
285-            <span class="menuitem lastmenuitem"><a class="menuitem" href="{{SITE_ROOT}}/nodes/discover">Discover Nodes</a></span>
286+            <span class="menuitem lastmenuitem"><a class="menuitem" href="{{SITE_ROOT}}/nodes/discover/">Discover Nodes</a></span>
287         </div>
288     {% endif %}
289     {% if tasks %}
290diff --git a/pydra_server/templates/nodes.html b/pydra_server/templates/nodes.html
291index edf23cf..58bc13f 100644
292--- a/pydra_server/templates/nodes.html
293+++ b/pydra_server/templates/nodes.html
294@@ -298,7 +298,7 @@
295 {% block submenu %}
296     {% if nodes and perms.pydra_server.can_edit_nodes %}
297         <span class="menuitem"><a class="menuitem" href="{{SITE_ROOT}}/nodes/edit">Create Node</a></span>
298-        <span class="menuitem lastmenuitem"><a class="menuitem" href="{{SITE_ROOT}}/nodes/discover">Discover Nodes</a></span>
299+        <span class="menuitem lastmenuitem"><a class="menuitem" href="{{SITE_ROOT}}/nodes/discover/">Discover Nodes</a></span>
300     {% endif %}
301 {% endblock %}
302 
303diff --git a/pydra_server/urls.py b/pydra_server/urls.py
304index d81873e..b760d24 100644
305--- a/pydra_server/urls.py
306+++ b/pydra_server/urls.py
307@@ -29,6 +29,7 @@ urlpatterns = patterns('',
308 
309     # node urls
310     (r'^nodes/$', nodes),
311+    (r'^nodes/discover/$', discover),
312     (r'^nodes/edit/(\d?)$', node_edit),
313     (r'^nodes/status/$', node_status),
314 
315diff --git a/pydra_server/views.py b/pydra_server/views.py
316index e06847f..3062ff5 100644
317--- a/pydra_server/views.py
318+++ b/pydra_server/views.py
319@@ -134,6 +134,25 @@ def node_edit(request, id=None):
320     }, context_instance=RequestContext(request, processors=[settings_processor]))
321 
322 
323+@user_passes_test(lambda u: u.has_perm('pydra_server.can_edit_nodes'))
324+def discover(request):
325+    """
326+    allow users to activate the nodes that have been discovered via avahi
327+    """
328+    from django import forms
329+    global pydra_controller
330+
331+    if request.method == 'POST':
332+        reconnect = False
333+        for i in request.POST.keys():
334+            host, port = i.split(':')
335+            if not Node.objects.filter(host=host, port=port):
336+                Node.objects.create(host=host, port=port)
337+                reconnect = True
338+        if reconnect:
339+            pydra_controller.remote_reconnect_nodes()
340+    return render_to_response('discover.html', {'known_nodes': pydra_controller.remote_list_known_nodes()})
341+
342 def node_status(request):
343     """
344     Retrieves Status of nodes
345diff --git a/urls.py b/urls.py
346index fa916a8..a2723ab 100644
347--- a/urls.py
348+++ b/urls.py
349@@ -26,5 +26,6 @@ admin.autodiscover()
350 urlpatterns = patterns('',
351     (r'^', include('pydra_server.urls')),
352     (r'^admin/(.*)', admin.site.root),
353+    (r'^settings/', include('dbsettings.urls')),
354 
355 )