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 for line in traceback.format_exception(*sys.exc_info()):
65 self.cl.send(" " + line)
66 self.cl.send("+EXC\n")
68 def handle(self, buf):
75 protocols["repl"] = repl
78 """PERF protocol handler
80 The PERF protocol provides an interface for program interaction
81 with the server process. It allows limited remote interactions
82 with Python objects over a few defined interfaces.
84 All objects that wish to be available for interaction need to
85 implement a method named `pdm_protocols' which, when called with
86 no arguments, should return a list of strings, each indicating a
87 PERF interface that the object implements. For each such
88 interface, the object must implement additional methods as
91 A client can find PERF objects to interact with either by
92 specifying the name of such an object in an existing module, or by
93 using the `dir' interface, described below. Thus, to make a PERF
94 object available for clients, it needs only be bound to a global
95 variable in a module and implement the `pdm_protocols'
96 method. When requesting an object from a module, the module must
97 already be imported. PDM will not import new modules for clients;
98 rather, the daemon process needs to import all modules that
99 clients should be able to interact with. PDM itself always imports
100 the L{pdm.perf} module, which contains a few basic PERF
101 objects. See its documentation for details.
103 The following interfaces are currently known to PERF.
106 An object that implements the `attr' interface models an
107 attribute that can be read by clients. The attribute can be
108 anything, as long as its representation can be
109 pickled. Examples of attributes could be such things as the CPU
110 time consumed by the server process, or the number of active
111 connections to whatever clients the program serves. To
112 implement the `attr' interface, an object must implement
113 methods called `readattr' and `attrinfo'. `readattr' is called
114 with no arguments to read the current value of the attribute,
115 and `attrinfo' is called with no arguments to read a
116 description of the attribute. Both should be
117 idempotent. `readattr' can return any pickleable object, and
118 `attrinfo' should return either None to indicate that it has no
119 description, or an instance of the L{pdm.perf.attrinfo} class.
122 The `dir' interface models a directory of other PERF
123 objects. An object implementing it must implement methods
124 called `lookup' and `listdir'. `lookup' is called with a single
125 string argument that names an object, and should either return
126 another PERF object based on the name, or raise KeyError if it
127 does not recognize the name. `listdir' is called with no
128 arguments, and should return a list of known names that can be
129 used as argument to `lookup', but the list is not required to
130 be exhaustive and may also be empty.
133 The `invoke' interface allows a more arbitrary form of method
134 calls to objects implementing it. Such objects must implement a
135 method called `invoke', which is called with one positional
136 argument naming a method to be called (which it is free to
137 interpret however it wishes), and with any additional
138 positional and keyword arguments that the client wishes to pass
139 to it. Whatever `invoke' returns is pickled and sent back to
140 the client. In case the method name is not recognized, `invoke'
141 should raise an AttributeError.
144 The `event' interface allows PERF objects to notify clients of
145 events asynchronously. Objects implementing it must implement
146 methods called `subscribe' and `unsubscribe'. `subscribe' will
147 be called with a single argument, which is a callable of one
148 argument, which should be registered to be called when an event
149 pertaining to the `event' object in question occurs. The
150 `event' object should then call all such registered callables
151 with a single argument describing the event. The argument could
152 be any object that can be pickled, but should be an instance of
153 a subclass of the L{pdm.perf.event} class. If `subscribe' is
154 called with a callback object that it has already registered,
155 it should raise a ValueError. `unsubscribe' is called with a
156 single argument, which is a previously registered callback
157 object, which should then be unregistered to that it is no
158 longer called when an event occurs. If the given callback
159 object is not, in fact, registered, a ValueError should be
162 The L{pdm.perf} module contains a few convenience classes which
163 implements the interfaces, but PERF objects are not required to be
164 instances of them. Any object can implement a PERF interface, as
165 long as it does so as described above.
167 The L{pdm.cli.perfclient} class is the client-side implementation.
169 def __init__(self, cl):
174 self.lock = threading.Lock()
178 for id, recv in self.subscribed.iteritems():
180 if ob is None: continue
186 def send(self, *args):
189 buf = pickle.dumps(args)
190 buf = struct.pack(">l", len(buf)) + buf
195 def bindob(self, id, ob):
196 if not hasattr(ob, "pdm_protocols"):
197 raise ValueError("Object does not support PDM introspection")
199 proto = ob.pdm_protocols()
200 except Exception, exc:
201 raise ValueError("PDM introspection failed", exc)
202 self.odtab[id] = ob, proto
205 def bind(self, id, module, obnm):
206 resmod = sys.modules.get(module)
208 self.send("-", ImportError("No such module: %s" % module))
211 ob = getattr(resmod, obnm)
212 except AttributeError:
213 self.send("-", AttributeError("No such object: %s" % obnm))
216 proto = self.bindob(id, ob)
217 except Exception, exc:
220 self.send("+", proto)
222 def getob(self, id, proto):
223 ob = self.odtab.get(id)
225 self.send("-", ValueError("No such bound ID: %r" % id))
228 if proto not in protos:
229 self.send("-", ValueError("Object does not support that protocol"))
233 def lookup(self, tgtid, srcid, obnm):
234 src = self.getob(srcid, "dir")
238 ob = src.lookup(obnm)
239 except KeyError, exc:
243 proto = self.bindob(tgtid, ob)
244 except Exception, exc:
247 self.send("+", proto)
249 def unbind(self, id):
250 ob = self.odtab.get(id)
252 self.send("-", KeyError("No such name bound: %r" % id))
256 recv = self.subscribed.get(id)
259 del self.subscribed[id]
262 def listdir(self, id):
263 ob = self.getob(id, "dir")
266 self.send("+", ob.listdir())
268 def readattr(self, id):
269 ob = self.getob(id, "attr")
274 except Exception, exc:
275 self.send("-", Exception("Could not read attribute"))
279 def attrinfo(self, id):
280 ob = self.getob(id, "attr")
283 self.send("+", ob.attrinfo())
285 def invoke(self, id, method, args, kwargs):
286 ob = self.getob(id, "invoke")
290 self.send("+", ob.invoke(method, *args, **kwargs))
291 except Exception, exc:
294 def event(self, id, ob, ev):
295 self.send("*", id, ev)
297 def subscribe(self, id):
298 ob = self.getob(id, "event")
301 if id in self.subscribed:
302 self.send("-", ValueError("Already subscribed"))
304 self.event(id, ob, ev)
306 self.subscribed[id] = recv
309 def unsubscribe(self, id):
310 ob = self.getob(id, "event")
313 recv = self.subscribed.get(id)
315 self.send("-", ValueError("Not subscribed"))
317 del self.subscribed[id]
320 def command(self, data):
324 elif cmd == "unbind":
325 self.unbind(*data[1:])
326 elif cmd == "lookup":
327 self.lookup(*data[1:])
329 self.listdir(*data[1:])
330 elif cmd == "readattr":
331 self.readattr(*data[1:])
332 elif cmd == "attrinfo":
333 self.attrinfo(*data[1:])
334 elif cmd == "invoke":
335 self.invoke(*data[1:])
337 self.subscribe(*data[1:])
338 elif cmd == "unsubs":
339 self.unsubscribe(*data[1:])
341 self.send("-", Exception("Unknown command: %r" % (cmd,)))
343 def handle(self, buf):
346 dlen = struct.unpack(">l", buf[:4])[0]
347 if len(buf) < dlen + 4:
349 data = pickle.loads(buf[4:dlen + 4])
351 return buf[dlen + 4:]
353 protocols["perf"] = perf
355 class client(threading.Thread):
356 def __init__(self, sk):
357 super(client, self).__init__(name = "Management client")
362 def send(self, data):
363 return self.sk.send(data)
365 def choose(self, proto):
366 if proto in protocols:
367 self.handler = protocols[proto](self)
369 self.send("-ERR Unknown protocol: %s\n" % proto)
372 def handle(self, buf):
385 ret = self.sk.recv(1024)
391 nbuf = self.handler.handle(buf)
398 #for line in traceback.format_exception(*sys.exc_info()):
403 if hasattr(self.handler, "closed"):
404 self.handler.closed()
407 class listener(threading.Thread):
410 This subclass of a thread listens to PDM connections and handles
411 client connections properly. It is intended to be subclassed by
412 providers of specific domains, such as unixlistener and
416 super(listener, self).__init__(name = "Management listener")
419 def listen(self, sk):
420 """Listen for and accept connections."""
423 rfd, wfd, efd = select.select([sk], [], [sk], 1)
426 nsk, addr = sk.accept()
427 self.accept(nsk, addr)
430 """Stop listening for client connections
432 Tells the listener thread to stop listening, and then waits
438 def accept(self, sk, addr):
442 class unixlistener(listener):
443 """Unix socket listener"""
444 def __init__(self, name, mode = 0600, group = None):
445 """Create a listener that will bind to the Unix socket named
446 by `name'. The socket will not actually be bound until the
447 listener is started. The socket will be chmodded to `mode',
448 and if `group' is given, the named group will be set as the
451 super(unixlistener, self).__init__()
457 sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
460 if os.path.exists(self.name) and os.path.stat.S_ISSOCK(os.stat(self.name).st_mode):
464 os.chmod(self.name, self.mode)
465 if self.group is not None:
466 os.chown(self.name, os.getuid(), grp.getgrnam(self.group).gr_gid)
474 class tcplistener(listener):
475 """TCP socket listener"""
476 def __init__(self, port, bindaddr = "127.0.0.1"):
477 """Create a listener that will bind to the given TCP port, and
478 the given local interface. The socket will not actually be
479 bound until the listener is started.
481 super(tcplistener, self).__init__()
483 self.bindaddr = bindaddr
486 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
488 sk.bind((self.bindaddr, self.port))
495 """Create and start a listener according to a string
496 specification. The string specifications can easily be passed from
497 command-line options, user configuration or the like. Currently,
498 the two following specification formats are recognized:
500 PATH[:MODE[:GROUP]] -- PATH must contain at least one slash. A
501 Unix socket listener will be created listening to that path, and
502 the socket will be chmodded to MODE and owned by GROUP. If MODE is
503 not given, it defaults to 0600, and if GROUP is not given, the
504 process' default group is used.
506 ADDRESS:PORT -- PORT must be entirely numeric. A TCP socket
507 listener will be created listening to that port, bound to the
508 given local interface address. Since PDM has no authentication
509 support, ADDRESS should probably be localhost.
512 first = spec[:spec.index(":")]
513 last = spec[spec.rindex(":") + 1:]
518 parts = spec.split(":")
522 mode = int(parts[1], 8)
525 ret = unixlistener(parts[0], mode = mode, group = group)
531 port = int(spec[p + 1:])
532 ret = tcplistener(port, bindaddr = host)
535 raise ValueError("Unparsable listener specification: %r" % spec)