1 """Python Daemon Management -- Server functions
3 This module implements the server part of the PDM protocols. The
4 primary object of interest herein is the listen() function, which is
5 the most generic way to create PDM listeners based on user
6 configuration, and the documentation for the repl and perf classes,
7 which describes the functioning of the REPL and PERF protocols.
10 import os, sys, socket, threading, grp, select
11 import types, pprint, traceback
14 __all__ = ["repl", "perf", "listener", "unixlistener", "tcplistener", "listen"]
19 """REPL protocol handler
21 Provides a read-eval-print loop. The primary client-side interface
22 is the L{pdm.cli.replclient} class. Clients can send arbitrary
23 code, which is compiled and run on its own thread in the server
24 process, and output responses that are echoed back to the client.
26 Each client is provided with its own module, in which the code
27 runs. The module is prepared with a function named `echo', which
28 takes a single object and pretty-prints it as part of the command
29 response. If a command can be parsed as an expression, the value
30 it evaluates to is automatically echoed to the client. If the
31 evalution of the command terminates with an exception, its
32 traceback is echoed to the client.
34 The REPL protocol is only intended for interactive usage. In order
35 to interact programmatically with the server process, see the PERF
38 def __init__(self, cl):
40 self.mod = types.ModuleType("repl")
41 self.mod.echo = self.echo
42 self.printer = pprint.PrettyPrinter(indent = 4, depth = 6)
45 def sendlines(self, text):
46 for line in text.split("\n"):
47 self.cl.send(" " + line + "\n")
50 self.sendlines(self.printer.pformat(ob))
52 def command(self, cmd):
55 ccode = compile(cmd, "PDM Input", "eval")
57 ccode = compile(cmd, "PDM Input", "exec")
58 exec ccode in self.mod.__dict__
61 self.echo(eval(ccode, self.mod.__dict__))
64 lines = ("".join(traceback.format_exception(*sys.exc_info()))).split("\n")
65 while len(lines) > 0 and lines[-1] == "": lines = lines[:-1]
67 self.cl.send(" " + line + "\n")
68 self.cl.send("+EXC\n")
70 def handle(self, buf):
77 protocols["repl"] = repl
80 """PERF protocol handler
82 The PERF protocol provides an interface for program interaction
83 with the server process. It allows limited remote interactions
84 with Python objects over a few defined interfaces.
86 All objects that wish to be available for interaction need to
87 implement a method named `pdm_protocols' which, when called with
88 no arguments, should return a list of strings, each indicating a
89 PERF interface that the object implements. For each such
90 interface, the object must implement additional methods as
93 A client can find PERF objects to interact with either by
94 specifying the name of such an object in an existing module, or by
95 using the `dir' interface, described below. Thus, to make a PERF
96 object available for clients, it needs only be bound to a global
97 variable in a module and implement the `pdm_protocols'
98 method. When requesting an object from a module, the module must
99 already be imported. PDM will not import new modules for clients;
100 rather, the daemon process needs to import all modules that
101 clients should be able to interact with. PDM itself always imports
102 the L{pdm.perf} module, which contains a few basic PERF
103 objects. See its documentation for details.
105 The following interfaces are currently known to PERF.
108 An object that implements the `attr' interface models an
109 attribute that can be read by clients. The attribute can be
110 anything, as long as its representation can be
111 pickled. Examples of attributes could be such things as the CPU
112 time consumed by the server process, or the number of active
113 connections to whatever clients the program serves. To
114 implement the `attr' interface, an object must implement
115 methods called `readattr' and `attrinfo'. `readattr' is called
116 with no arguments to read the current value of the attribute,
117 and `attrinfo' is called with no arguments to read a
118 description of the attribute. Both should be
119 idempotent. `readattr' can return any pickleable object, and
120 `attrinfo' should return either None to indicate that it has no
121 description, or an instance of the L{pdm.perf.attrinfo} class.
124 The `dir' interface models a directory of other PERF
125 objects. An object implementing it must implement methods
126 called `lookup' and `listdir'. `lookup' is called with a single
127 string argument that names an object, and should either return
128 another PERF object based on the name, or raise KeyError if it
129 does not recognize the name. `listdir' is called with no
130 arguments, and should return a list of known names that can be
131 used as argument to `lookup', but the list is not required to
132 be exhaustive and may also be empty.
135 The `invoke' interface allows a more arbitrary form of method
136 calls to objects implementing it. Such objects must implement a
137 method called `invoke', which is called with one positional
138 argument naming a method to be called (which it is free to
139 interpret however it wishes), and with any additional
140 positional and keyword arguments that the client wishes to pass
141 to it. Whatever `invoke' returns is pickled and sent back to
142 the client. In case the method name is not recognized, `invoke'
143 should raise an AttributeError.
146 The `event' interface allows PERF objects to notify clients of
147 events asynchronously. Objects implementing it must implement
148 methods called `subscribe' and `unsubscribe'. `subscribe' will
149 be called with a single argument, which is a callable of one
150 argument, which should be registered to be called when an event
151 pertaining to the `event' object in question occurs. The
152 `event' object should then call all such registered callables
153 with a single argument describing the event. The argument could
154 be any object that can be pickled, but should be an instance of
155 a subclass of the L{pdm.perf.event} class. If `subscribe' is
156 called with a callback object that it has already registered,
157 it should raise a ValueError. `unsubscribe' is called with a
158 single argument, which is a previously registered callback
159 object, which should then be unregistered to that it is no
160 longer called when an event occurs. If the given callback
161 object is not, in fact, registered, a ValueError should be
164 The L{pdm.perf} module contains a few convenience classes which
165 implements the interfaces, but PERF objects are not required to be
166 instances of them. Any object can implement a PERF interface, as
167 long as it does so as described above.
169 The L{pdm.cli.perfclient} class is the client-side implementation.
171 def __init__(self, cl):
176 self.lock = threading.Lock()
180 for id, recv in self.subscribed.iteritems():
182 if ob is None: continue
188 def send(self, *args):
191 buf = pickle.dumps(args)
192 buf = struct.pack(">l", len(buf)) + buf
197 def bindob(self, id, ob):
198 if not hasattr(ob, "pdm_protocols"):
199 raise ValueError("Object does not support PDM introspection")
201 proto = ob.pdm_protocols()
202 except Exception, exc:
203 raise ValueError("PDM introspection failed", exc)
204 self.odtab[id] = ob, proto
207 def bind(self, id, module, obnm):
208 resmod = sys.modules.get(module)
210 self.send("-", ImportError("No such module: %s" % module))
213 ob = getattr(resmod, obnm)
214 except AttributeError:
215 self.send("-", AttributeError("No such object: %s" % obnm))
218 proto = self.bindob(id, ob)
219 except Exception, exc:
222 self.send("+", proto)
224 def getob(self, id, proto):
225 ob = self.odtab.get(id)
227 self.send("-", ValueError("No such bound ID: %r" % id))
230 if proto not in protos:
231 self.send("-", ValueError("Object does not support that protocol"))
235 def lookup(self, tgtid, srcid, obnm):
236 src = self.getob(srcid, "dir")
240 ob = src.lookup(obnm)
241 except KeyError, exc:
245 proto = self.bindob(tgtid, ob)
246 except Exception, exc:
249 self.send("+", proto)
251 def unbind(self, id):
252 ob = self.odtab.get(id)
254 self.send("-", KeyError("No such name bound: %r" % id))
258 recv = self.subscribed.get(id)
261 del self.subscribed[id]
264 def listdir(self, id):
265 ob = self.getob(id, "dir")
268 self.send("+", ob.listdir())
270 def readattr(self, id):
271 ob = self.getob(id, "attr")
276 except Exception, exc:
277 self.send("-", Exception("Could not read attribute"))
281 def attrinfo(self, id):
282 ob = self.getob(id, "attr")
285 self.send("+", ob.attrinfo())
287 def invoke(self, id, method, args, kwargs):
288 ob = self.getob(id, "invoke")
292 self.send("+", ob.invoke(method, *args, **kwargs))
293 except Exception, exc:
296 def event(self, id, ob, ev):
297 self.send("*", id, ev)
299 def subscribe(self, id):
300 ob = self.getob(id, "event")
303 if id in self.subscribed:
304 self.send("-", ValueError("Already subscribed"))
306 self.event(id, ob, ev)
308 self.subscribed[id] = recv
311 def unsubscribe(self, id):
312 ob = self.getob(id, "event")
315 recv = self.subscribed.get(id)
317 self.send("-", ValueError("Not subscribed"))
319 del self.subscribed[id]
322 def command(self, data):
326 elif cmd == "unbind":
327 self.unbind(*data[1:])
328 elif cmd == "lookup":
329 self.lookup(*data[1:])
331 self.listdir(*data[1:])
332 elif cmd == "readattr":
333 self.readattr(*data[1:])
334 elif cmd == "attrinfo":
335 self.attrinfo(*data[1:])
336 elif cmd == "invoke":
337 self.invoke(*data[1:])
339 self.subscribe(*data[1:])
340 elif cmd == "unsubs":
341 self.unsubscribe(*data[1:])
343 self.send("-", Exception("Unknown command: %r" % (cmd,)))
345 def handle(self, buf):
348 dlen = struct.unpack(">l", buf[:4])[0]
349 if len(buf) < dlen + 4:
351 data = pickle.loads(buf[4:dlen + 4])
353 return buf[dlen + 4:]
355 protocols["perf"] = perf
357 class client(threading.Thread):
358 def __init__(self, sk):
359 super(client, self).__init__(name = "Management client")
364 def send(self, data):
365 return self.sk.send(data)
367 def choose(self, proto):
368 if proto in protocols:
369 self.handler = protocols[proto](self)
371 self.send("-ERR Unknown protocol: %s\n" % proto)
374 def handle(self, buf):
387 ret = self.sk.recv(1024)
393 nbuf = self.handler.handle(buf)
400 #for line in traceback.format_exception(*sys.exc_info()):
405 if hasattr(self.handler, "closed"):
406 self.handler.closed()
409 class listener(threading.Thread):
412 This subclass of a thread listens to PDM connections and handles
413 client connections properly. It is intended to be subclassed by
414 providers of specific domains, such as unixlistener and
418 super(listener, self).__init__(name = "Management listener")
421 def listen(self, sk):
422 """Listen for and accept connections."""
425 rfd, wfd, efd = select.select([sk], [], [sk], 1)
428 nsk, addr = sk.accept()
429 self.accept(nsk, addr)
432 """Stop listening for client connections
434 Tells the listener thread to stop listening, and then waits
440 def accept(self, sk, addr):
444 class unixlistener(listener):
445 """Unix socket listener"""
446 def __init__(self, name, mode = 0600, group = None):
447 """Create a listener that will bind to the Unix socket named
448 by `name'. The socket will not actually be bound until the
449 listener is started. The socket will be chmodded to `mode',
450 and if `group' is given, the named group will be set as the
453 super(unixlistener, self).__init__()
459 sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
462 if os.path.exists(self.name) and os.path.stat.S_ISSOCK(os.stat(self.name).st_mode):
466 os.chmod(self.name, self.mode)
467 if self.group is not None:
468 os.chown(self.name, os.getuid(), grp.getgrnam(self.group).gr_gid)
476 class tcplistener(listener):
477 """TCP socket listener"""
478 def __init__(self, port, bindaddr = "127.0.0.1"):
479 """Create a listener that will bind to the given TCP port, and
480 the given local interface. The socket will not actually be
481 bound until the listener is started.
483 super(tcplistener, self).__init__()
485 self.bindaddr = bindaddr
488 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
490 sk.bind((self.bindaddr, self.port))
497 """Create and start a listener according to a string
498 specification. The string specifications can easily be passed from
499 command-line options, user configuration or the like. Currently,
500 the two following specification formats are recognized:
502 PATH[:MODE[:GROUP]] -- PATH must contain at least one slash. A
503 Unix socket listener will be created listening to that path, and
504 the socket will be chmodded to MODE and owned by GROUP. If MODE is
505 not given, it defaults to 0600, and if GROUP is not given, the
506 process' default group is used.
508 ADDRESS:PORT -- PORT must be entirely numeric. A TCP socket
509 listener will be created listening to that port, bound to the
510 given local interface address. Since PDM has no authentication
511 support, ADDRESS should probably be localhost.
514 first = spec[:spec.index(":")]
515 last = spec[spec.rindex(":") + 1:]
520 parts = spec.split(":")
524 mode = int(parts[1], 8)
527 ret = unixlistener(parts[0], mode = mode, group = group)
533 port = int(spec[p + 1:])
534 ret = tcplistener(port, bindaddr = host)
537 raise ValueError("Unparsable listener specification: %r" % spec)