root/projects/AsynCluster/trunk/asyncluster/master/nodes.py

Revision 125, 6.5 kB (checked in by edsuom, 10 months ago)

Node/worker clients nearing completion

Line 
1 # AsynCluster: Master
2 # A cluster management server based on Twisted's Perspective Broker. Dispatches
3 # cluster jobs and regulates when and how much each user can use his account on
4 # any of the cluster node workstations.
5 #
6 # Copyright (C) 2006-2008 by Edwin A. Suominen, http://www.eepatents.com
7 #
8 # This program is free software; you can redistribute it and/or modify it under
9 # the terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
11 # version.
12 #
13 # This program is distributed in the hope that it will be useful, but WITHOUT
14 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 # FOR A PARTICULAR PURPOSE.  See the file COPYING for more details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # this program; if not, write to the Free Software Foundation, Inc., 51
19 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20
21 """
22 The PB server for node-master TCP connections.
23 """
24
25 from zope.interface import implements
26 from twisted.internet import defer, interfaces
27 from twisted.cred import credentials, checkers, portal, error
28 from twisted.python.failure import Failure
29 from twisted.spread import pb
30
31 from twisted_goodies.misc import AddressRestrictorMixin
32
33
34 class Perspective(pb.Avatar):
35     """
36     Each PB node and worker client receives a reference to its very own
37     instance of me as its perspective upon making an authenticated TCP
38     connection to the node master server.
39
40     @ivar ID: A unique ID for this client, established during a mutually
41       authenticated client-server connection.
42
43     @ivar userID: The ID of any user having a session underway on the client if
44       it is a node rather than a worker.
45
46     @ivar nodeClient: A Boolean that is set C{True} if my client is a node
47       client, as opposed to a child worker client.
48     
49     """
50     def __init__(self, ctl):
51         self.ctl = ctl
52
53     def perspective_getSessionManager(self):
54         """
55         Remotely-accessible wrapper for
56         L{control.Controller.getSessionManager}.
57         """
58         return self.ctl.getSessionManager()
59
60     def _printableID(self):
61         return "%s%04d" % ("WN"[self.nodeClient], self.ID)
62
63     def attached(self, clientRoot):
64         """
65         Called by the node client L{Realm}, after a successful login, with a
66         reference to the I{clientRoot} object supplied to it as the
67         incomprehensibly named 'mind'.
68
69         Performs a reverse login to the client to satisfy it that I am running
70         on a trustworthy server and the arbitrary Python code it receives from
71         this server to execute for computing jobs will not do bad things to it.
72         """
73         def responded(acceptanceCode):
74             if acceptanceCode == 'node':
75                 self.nodeClient = True
76                 d = self.ctl.attachNode(self, clientRoot)
77             elif acceptanceCode == 'child':
78                 self.nodeClient = False
79                 d = self.ctl.attachWorker(clientRoot)
80             else:
81                 return (pb.IPerspective, self, lambda : None)
82             clientRoot.notifyOnDisconnect(self.detached)
83             return d.addCallback(doneAttaching)
84    
85         def doneAttaching(ID):
86             self.ID = ID
87             print "%s: Attached" % self._printableID()
88             return pb.IPerspective, self, self.detached
89
90         serverPassword = self.ctl.config['common']['server password']
91         d = clientRoot.callRemote('reverseLogin', serverPassword)
92         return d.addCallback(responded)
93
94     def detached(self, *null):
95         """
96         Called when the client disconnects.
97         """
98         if hasattr(self, 'ID'):
99             print "%s: Detached" % self._printableID()
100             if self.nodeClient:
101                 d = self.ctl.detachNode(self.ID)
102             else:
103                 d = self.ctl.detachWorker(self.ID)
104             del self.ID
105             return d
106
107
108 class PasswordChecker(object):
109     """
110     Checks hashed passwords based on the 'client' section of the config file.
111     """
112     implements(checkers.ICredentialsChecker)
113
114     credentialInterfaces = (credentials.IUsernameHashedPassword,)
115
116     def __init__(self, clientSection):
117         self.clientSection = clientSection
118    
119     def requestAvatarId(self, credentials):
120         def possiblyMatched(matched, user):
121             if matched:
122                 return user
123             return Failure(error.UnauthorizedLogin())
124
125         user = credentials.username
126         if user != self.clientSection['user']:
127             d = defer.succeed(False)
128         else:
129             password = self.clientSection.get('password', None)
130             if password is None:
131                 d = defer.succeed(False)
132             else:
133                 d = defer.maybeDeferred(credentials.checkPassword, password)
134         d.addCallback(possiblyMatched, user)
135         return d
136
137
138 class Realm(object):
139     """
140     Construct me with to a reference to the L{control.Controller} object that
141     controls everything.
142     """
143     implements(portal.IRealm)
144
145     def __init__(self, ctl):
146         self.ctl = ctl
147
148     def requestAvatar(self, avatarID, mind, *interfaces):
149         """
150         Returns a deferred that fires with the required I{interface,
151         perspective, logout} tuple after the perspective attempts a reverse
152         login to the client.
153         """
154         if pb.IPerspective not in interfaces:
155             raise NotImplementedError(self, interfaces)
156         perspective = Perspective(self.ctl)
157         return perspective.attached(mind)
158
159
160 class ServerFactory(AddressRestrictorMixin, pb.PBServerFactory):
161     """
162     I am a PB server factory for the NDM node-master TCP server, which only
163     accepts connections from one or more IP address subnets, defined in the
164     config file as a comma-separated list of base/bits strings, e.g.,
165     192.168.1.0/24.
166
167     Construct me with a reference to the L{control.Controller} object that
168     controls everything. It must have a public attribute 'config' referencing a
169     config object loaded with the NDM configuration file.
170     """
171     def __init__(self, ctl, checker=None):
172         # The checker keyword is for testing with a simple checker
173         for subnetString in ctl.config['server']['subnets']:
174             self.addSubnet(subnetString.strip())
175         rootPortal = portal.Portal(Realm(ctl))
176         if checker is None:
177             checker = PasswordChecker(ctl.config['client'])
178         rootPortal.registerChecker(checker)
179         pb.PBServerFactory.__init__(self, rootPortal)
180
181    
182    
183
184
185        
Note: See TracBrowser for help on using the browser.