1 """Python Daemon Management -- Client functions
3 This module implements the client part of the PDM protocols. The
4 primary objects of interest are the replclient and perfclient classes,
5 which implement support for their respective protocols. See their
6 documentation for details.
9 import socket, pickle, struct, select, threading
11 __all__ = ["client", "replclient", "perfclient"]
13 class protoerr(Exception):
14 """Raised on protocol errors"""
18 if isinstance(spec, socket.socket):
24 first, second = spec[:p], spec[p + 1:]
27 sk = sshsock.sshsocket(first, second)
29 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
30 sk.connect((first, second))
32 sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
35 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
36 sk.connect(("localhost", int(spec)))
38 raise Exception("Unknown target specification %r" % spec)
42 if sk is not None: sk.close()
48 This class provides general facilities to speak to PDM servers,
49 and is mainly intended to be subclassed to provide for the
50 specific protocols, such as replclient and perfclient do.
52 `client' instances can be passed as arguments to select.select(),
53 and can be used in `with' statements.
55 def __init__(self, sk, proto = None):
56 """Create a client object connected to the specified
57 server. `sk' can either be a socket object, which is used as
58 it is, or a string specification very similar to the
59 specification for L{pdm.srv.listen}, so see its documentation
60 for details. The differences are only that this function does
61 not take arguments specific to socket creation, like the mode
62 and group arguments for Unix sockets. If `proto' is given,
63 that subprotocol will negotiated with the server (by calling
68 line = self.readline()
70 raise protoerr("Illegal protocol signature")
75 """Close this connection"""
79 """Return the file descriptor of the underlying socket."""
80 return self.sk.fileno()
83 """Read a single NL-terminated line and return it."""
85 p = self.buf.find("\n")
88 self.buf = self.buf[p + 1:]
90 ret = self.sk.recv(1024)
95 def select(self, proto):
96 """Negotiate the given subprotocol with the server"""
98 raise Exception("Illegal protocol specified: %r" % proto)
99 self.sk.send(proto + "\n")
100 rep = self.readline()
101 if len(rep) < 1 or rep[0] != "+":
102 raise protoerr("Error reply when selecting protocol %s: %s" % (proto, rep[1:]))
107 def __exit__(self, *excinfo):
111 class replclient(client):
112 """REPL protocol client
114 Implements the client side of the REPL protocol; see
115 L{pdm.srv.repl} for details on the protocol and its functionality.
117 def __init__(self, sk):
118 """Create a connected client as documented in the `client' class."""
119 super(replclient, self).__init__(sk, "repl")
122 """Run a single block of Python code on the server. Returns
123 the output of the command (as documented in L{pdm.srv.repl})
127 ncode = code.replace("\n\n", "\n")
128 if ncode == code: break
130 while len(code) > 0 and code[-1] == "\n":
132 self.sk.send(code + "\n\n")
141 raise protoerr("Error reply: %s" % ln[1:])
143 raise protoerr("Illegal reply: %s" % ln)
145 class perfproxy(object):
146 def __init__(self, cl, id, proto):
150 self.subscribers = set()
152 def lookup(self, name):
153 self.cl.lock.acquire()
158 self.cl.lock.release()
159 (proto,) = self.cl.run("lookup", id, self.id, name)
160 proxy = perfproxy(self.cl, id, proto)
161 self.cl.proxies[id] = proxy
165 return self.cl.run("ls", self.id)[0]
168 return self.cl.run("readattr", self.id)[0]
171 return self.cl.run("attrinfo", self.id)[0]
173 def invoke(self, method, *args, **kwargs):
174 return self.cl.run("invoke", self.id, method, args, kwargs)[0]
176 def subscribe(self, cb):
177 if cb in self.subscribers:
178 raise ValueError("Already subscribed")
179 if len(self.subscribers) == 0:
180 self.cl.run("subs", self.id)
181 self.subscribers.add(cb)
183 def unsubscribe(self, cb):
184 if cb not in self.subscribers:
185 raise ValueError("Not subscribed")
186 self.subscribers.remove(cb)
187 if len(self.subscribers) == 0:
188 self.cl.run("unsubs", self.id)
190 def notify(self, ev):
191 for cb in self.subscribers:
197 if self.id is not None:
198 self.cl.run("unbind", self.id)
199 del self.cl.proxies[self.id]
208 def __exit__(self, *excinfo):
212 class perfclient(client):
213 """PERF protocol client
215 Implements the client side of the PERF protocol; see
216 L{pdm.srv.perf} for details on the protocol and its functionality.
218 This client class implements functions for finding PERF objects on
219 the server, and returns, for each server-side object looked up, a
220 proxy object that mimics exactly the PERF interfaces that the
221 object implements. As the proxy objects reference live objects on
222 the server, they should be released when they are no longer used;
223 they implement a close() method for that purpose, and can also be
224 used in `with' statements.
226 See L{pdm.srv.perf} for details on the various PERF interfaces
227 that the proxy objects might implement.
229 def __init__(self, sk):
230 """Create a connected client as documented in the `client' class."""
231 super(perfclient, self).__init__(sk, "perf")
233 self.lock = threading.Lock()
238 buf = pickle.dumps(ob)
239 buf = struct.pack(">l", len(buf)) + buf
242 def recvb(self, num):
244 while len(buf) < num:
245 data = self.sk.recv(num - len(buf))
252 return pickle.loads(self.recvb(struct.unpack(">l", self.recvb(4))[0]))
254 def event(self, id, ev):
255 proxy = self.proxies.get(id)
256 if proxy is None: return
259 def dispatch(self, timeout = None):
260 """Wait for an incoming notification from the server, and
261 dispatch it to the callback functions that have been
262 registered for it. If `timeout' is specified, wait no longer
263 than so many seconds; otherwise, wait forever. This client
264 object may also be used as argument to select.select().
266 rfd, wfd, efd = select.select([self.sk], [], [], timeout)
270 self.event(msg[1], msg[2])
272 raise ValueError("Unexpected non-event message: %r" % msg[0])
277 if reply[0] in ("+", "-"):
279 elif reply[0] == "*":
280 self.event(reply[1], reply[2])
282 raise ValueError("Illegal reply header: %r" % reply[0])
284 def run(self, cmd, *args):
287 self.send((cmd,) + args)
288 reply = self.recvreply()
296 def lookup(self, module, obnm):
297 """Look up a single server-side object by the given name in
298 the given module. Will return a new proxy object for each
299 call when called multiple times for the same name.
307 (proto,) = self.run("bind", id, module, obnm)
308 proxy = perfproxy(self, id, proto)
309 self.proxies[id] = proxy
312 def find(self, name):
313 """Convenience function for looking up server-side objects
314 through PERF directories and for multiple uses. The object
315 name can be given as "MODULE.OBJECT", which will look up the
316 named OBJECT in the named MODULE, and can be followed by any
317 number of slash-separated names, which will assume that the
318 object to the left of the slash is a PERF directory, and will
319 return the object in that directory by the name to the right
320 of the slash. For instance, find("pdm.perf.sysres/cputime")
321 will return the built-in attribute for reading the CPU time
322 used by the server process.
324 The proxy objects returned by this function are cached and the
325 same object are returned the next time the same name is
326 requested, which means that they are kept live until the
327 client connection is closed.
329 ret = self.names.get(name)
333 ret = self.find(name[:p]).lookup(name[p + 1:])
336 ret = self.lookup(name[:p], name[p + 1:])
337 self.names[name] = ret