diff options
author | Chris St. Pierre <chris.a.st.pierre@gmail.com> | 2012-10-15 09:10:10 -0400 |
---|---|---|
committer | Chris St. Pierre <chris.a.st.pierre@gmail.com> | 2012-10-15 09:10:21 -0400 |
commit | 69faac9ae1d4498b4791af40a8e6bb877b82da77 (patch) | |
tree | 32b2f9e69edf1db568ae803b923a3b372798c50e | |
parent | af4158afe15a3dc2ce1d98b80836e130b00eac81 (diff) | |
download | bcfg2-69faac9ae1d4498b4791af40a8e6bb877b82da77.tar.gz bcfg2-69faac9ae1d4498b4791af40a8e6bb877b82da77.tar.bz2 bcfg2-69faac9ae1d4498b4791af40a8e6bb877b82da77.zip |
documented core implementations
-rw-r--r-- | doc/conf.py | 5 | ||||
-rw-r--r-- | doc/development/core.txt | 29 | ||||
-rw-r--r-- | src/lib/Bcfg2/SSLServer.py | 118 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/BuiltinCore.py | 40 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/CherryPyCore.py | 41 |
5 files changed, 134 insertions, 99 deletions
diff --git a/doc/conf.py b/doc/conf.py index 4dda8327f..96a1efbc7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -264,8 +264,9 @@ def setup(app): versions = ["3.2", "2.7", "2.6"] cur_version = '.'.join(str(v) for v in sys.version_info[0:2]) -intersphinx_mapping = dict(mock=('http://www.voidspace.org.uk/python/mock', - None)) +intersphinx_mapping = \ + dict(mock=('http://www.voidspace.org.uk/python/mock', None), + cherrypy=('http://docs.cherrypy.org/stable', None)) for pyver in versions: if pyver == cur_version: key = 'py' diff --git a/doc/development/core.txt b/doc/development/core.txt index 145be3338..3607533ea 100644 --- a/doc/development/core.txt +++ b/doc/development/core.txt @@ -20,23 +20,26 @@ written to take advantage of other technologies, e.g., `Tornado A core implementation needs to: -* Override :func:`Bcfg2.Server.Core.Core._daemonize` to handle +* Override :func:`Bcfg2.Server.Core.BaseCore._daemonize` to handle daemonization, writing the PID file, and dropping privileges. -* Override :func:`Bcfg2.Server.Core.Core._run` to handle server +* Override :func:`Bcfg2.Server.Core.BaseCore._run` to handle server startup. -* Override :func:`Bcfg2.Server.Core.Core._block` to run the blocking - server loop. -* Call :func:`Bcfg2.Server.Core.Core.shutdown` on orderly shutdown. +* Override :func:`Bcfg2.Server.Core.BaseCore._block` to run the + blocking server loop. +* Call :func:`Bcfg2.Server.Core.BaseCore.shutdown` on orderly + shutdown. Nearly all XML-RPC handling is delegated entirely to the core implementation. It needs to: -* Call :func:`Bcfg2.Server.Core.Core.authenticate` to authenticate +* Call :func:`Bcfg2.Server.Core.BaseCore.authenticate` to authenticate clients. * Handle :exc:`xmlrpclib.Fault` exceptions raised by the exposed XML-RPC methods as appropriate. * Dispatch XML-RPC method invocations to the appropriate method, - including Plugin RMI. + including Plugin RMI. The client address pair (a tuple of remote IP + address and remote hostname) must be prepended to the argument list + passed to built-in methods (i.e., not to plugin RMI). Additionally, running and configuring the server is delegated to the core. It needs to honor the configuration options that influence how @@ -54,8 +57,20 @@ Core Implementations Builtin Core ------------ +The builtin server core consists of the core implementation +(:class:`Bcfg2.Server.BuiltinCore.Core`) and the XML-RPC server +implementation (:mod:`Bcfg2.SSLServer`). + +Core +~~~~ + .. automodule:: Bcfg2.Server.BuiltinCore +XML-RPC Server +~~~~~~~~~~~~~~ + +.. automodule:: Bcfg2.SSLServer + CherryPy Core ------------- diff --git a/src/lib/Bcfg2/SSLServer.py b/src/lib/Bcfg2/SSLServer.py index fbcb0e347..5e3c6232a 100644 --- a/src/lib/Bcfg2/SSLServer.py +++ b/src/lib/Bcfg2/SSLServer.py @@ -1,8 +1,6 @@ -"""Bcfg2 SSL server.""" - -__all__ = [ - "SSLServer", "XMLRPCRequestHandler", "XMLRPCServer", -] +""" Bcfg2 SSL server used by the builtin server core +(:mod:`Bcfg2.Server.BuiltinCore`). This needs to be documented +better. """ import os import sys @@ -18,6 +16,8 @@ from Bcfg2.Compat import xmlrpclib, SimpleXMLRPCServer, SocketServer, \ class XMLRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): + """ An XML-RPC dispatcher. """ + logger = logging.getLogger("Bcfg2.SSLServer.XMLRPCDispatcher") def __init__(self, allow_none, encoding): @@ -33,7 +33,6 @@ class XMLRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): self.encoding = encoding def _marshaled_dispatch(self, address, data): - method_func = None params, method = xmlrpclib.loads(data) try: if '.' not in method: @@ -62,15 +61,7 @@ class XMLRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): class SSLServer(SocketServer.TCPServer, object): - """TCP server supporting SSL encryption. - - Methods: - handshake -- perform a SSL/TLS handshake - - Properties: - url -- A url pointing to this server. - - """ + """ TCP server supporting SSL encryption. """ allow_reuse_address = True logger = logging.getLogger("Bcfg2.SSLServer.SSLServer") @@ -78,19 +69,23 @@ class SSLServer(SocketServer.TCPServer, object): def __init__(self, listen_all, server_address, RequestHandlerClass, keyfile=None, certfile=None, reqCert=False, ca=None, timeout=None, protocol='xmlrpc/ssl'): - - """Initialize the SSL-TCP server. - - Arguments: - server_address -- address to bind to the server - RequestHandlerClass -- class to handle requests - - Keyword arguments: - keyfile -- private encryption key filename (enables ssl encryption) - certfile -- certificate file (enables ssl encryption) - reqCert -- client must present certificate - timeout -- timeout for non-blocking request handling - + """ + :param listen_all: Listen on all interfaces + :type listen_all: bool + :param server_address: Address to bind to the server + :param RequestHandlerClass: Request handler used by TCP server + :param keyfile: Full path to SSL encryption key file + :type keyfile: string + :param certfile: Full path to SSL certificate file + :type certfile: string + :param reqCert: Require client to present certificate + :type reqCert: bool + :param ca: Full path to SSL CA that signed the key and cert + :type ca: string + :param timeout: Timeout for non-blocking request handling + :param protocol: The protocol to serve. Supported values are + ``xmlrpc/ssl`` and ``xmlrpc/tlsv1``. + :type protocol: string """ # check whether or not we should listen on all interfaces if listen_all: @@ -183,19 +178,11 @@ class SSLServer(SocketServer.TCPServer, object): class XMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): - """Component XML-RPC request handler. + """ XML-RPC request handler. Adds support for HTTP authentication. - - Exceptions: - - CouldNotAuthenticate -- client did not present acceptable - authentication information - - Methods: - authenticate -- prompt a check of a client's provided username and password - handle_one_request -- handle a single rpc (optionally authenticating) """ + logger = logging.getLogger("Bcfg2.SSLServer.XMLRPCRequestHandler") def authenticate(self): @@ -325,50 +312,37 @@ class XMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): class XMLRPCServer(SocketServer.ThreadingMixIn, SSLServer, XMLRPCDispatcher, object): - """Component XMLRPCServer. - - Methods: - serve_daemon -- serve_forever in a daemonized process - serve_forever -- handle_one_request until not self.serve - shutdown -- stop serve_forever (by setting self.serve = False) - ping -- return all arguments received - - RPC methods: - ping - - (additional system.* methods are inherited from base dispatcher) - - Properties: - require_auth -- the request handler is requiring authorization - credentials -- valid credentials being used for authentication - """ + """ Component XMLRPCServer. """ def __init__(self, listen_all, server_address, RequestHandlerClass=None, keyfile=None, certfile=None, ca=None, protocol='xmlrpc/ssl', - timeout=10, - logRequests=False, + timeout=10, logRequests=False, register=True, allow_none=True, encoding=None): - """Initialize the XML-RPC server. - - Arguments: - server_address -- address to bind to the server - RequestHandlerClass -- request handler used by TCP server (optional) - - Keyword arguments: - keyfile -- private encryption key filename - certfile -- certificate file - logRequests -- log all requests (default False) - register -- presence should be reported to service-location - (default True) - allow_none -- allow None values in xml-rpc - encoding -- encoding to use for xml-rpc (default UTF-8) + """ + :param listen_all: Listen on all interfaces + :type listen_all: bool + :param server_address: Address to bind to the server + :param RequestHandlerClass: request handler used by TCP server + :param keyfile: Full path to SSL encryption key file + :type keyfile: string + :param certfile: Full path to SSL certificate file + :type certfile: string + :param ca: Full path to SSL CA that signed the key and cert + :type ca: string + :param logRequests: Log all requests + :type logRequests: bool + :param register: Presence should be reported to service-location + :type register: bool + :param allow_none: Allow None values in XML-RPC + :type allow_non: bool + :param encoding: Encoding to use for XML-RPC """ XMLRPCDispatcher.__init__(self, allow_none, encoding) if not RequestHandlerClass: # pylint: disable=E0102 - class RequestHandlerClass (XMLRPCRequestHandler): + class RequestHandlerClass(XMLRPCRequestHandler): """A subclassed request handler to prevent class-attribute conflicts.""" # pylint: enable=E0102 diff --git a/src/lib/Bcfg2/Server/BuiltinCore.py b/src/lib/Bcfg2/Server/BuiltinCore.py index 5b9c3302c..889e4d4d6 100644 --- a/src/lib/Bcfg2/Server/BuiltinCore.py +++ b/src/lib/Bcfg2/Server/BuiltinCore.py @@ -1,4 +1,4 @@ -""" the core of the builtin bcfg2 server """ +""" The core of the builtin Bcfg2 server. """ import os import sys @@ -12,8 +12,15 @@ from Bcfg2.SSLServer import XMLRPCServer class PidFile(object): - """ context handler to write the pid file """ + """ Context handler used by :class:`daemon.DaemonContext` to write + the PID file. """ + def __init__(self, pidfile): + """ + :param pidfile: The full path to the PID file to write on + entering the context handler + :type pidfile: string + """ self.pidfile = pidfile def __enter__(self): @@ -29,18 +36,32 @@ class Core(BaseCore): def __init__(self, setup): BaseCore.__init__(self, setup) + + #: The :class:`Bcfg2.SSLServer.XMLRPCServer` instance powering + #: this server core self.server = None + + #: The :class:`daemon.DaemonContext` used to drop privileges, + #: write the PID file (with :class:`PidFile`), and daemonize + #: this core. self.context = \ daemon.DaemonContext(uid=self.setup['daemon_uid'], gid=self.setup['daemon_gid'], pidfile=PidFile(self.setup['daemon'])) + __init__.__doc__ = BaseCore.__init__.__doc__.split('.. -----')[0] def _dispatch(self, method, args, dispatch_dict): - """Custom XML-RPC dispatcher for components. - - method -- XML-RPC method name - args -- tuple of paramaters to method + """ Dispatch XML-RPC method calls + :param method: XML-RPC method name + :type method: string + :param args: Paramaters to pass to the method + :type args: tuple + :param dispatch_dict: A dict of method name -> function that + can be used to provide custom mappings + :type dispatch_dict: dict + :returns: The return value of the method call + :raises: :exc:`xmlrpclib.Fault` """ if method in dispatch_dict: method_func = dispatch_dict[method] @@ -55,7 +76,7 @@ class Core(BaseCore): try: method_start = time.time() try: - result = method_func(*args) + return method_func(*args) finally: Bcfg2.Statistics.stats.add_value(method, time.time() - method_start) @@ -66,13 +87,15 @@ class Core(BaseCore): if getattr(err, "log", True): self.logger.error(err, exc_info=True) raise xmlrpclib.Fault(getattr(err, "fault_code", 1), str(err)) - return result def _daemonize(self): + """ Open :attr:`context` to drop privileges, write the PID + file, and daemonize the server core. """ self.context.open() self.logger.info("%s daemonized" % self.name) def _run(self): + """ Create :attr:`server` to start the server listening. """ hostname, port = urlparse(self.setup['location'])[1].split(':') server_address = socket.getaddrinfo(hostname, port, @@ -96,6 +119,7 @@ class Core(BaseCore): return True def _block(self): + """ Enter the blocking infinite loop. """ try: self.server.serve_forever() finally: diff --git a/src/lib/Bcfg2/Server/CherryPyCore.py b/src/lib/Bcfg2/Server/CherryPyCore.py index 53f3de018..1735fa5c7 100644 --- a/src/lib/Bcfg2/Server/CherryPyCore.py +++ b/src/lib/Bcfg2/Server/CherryPyCore.py @@ -1,4 +1,5 @@ -""" the core of the CherryPy-powered server """ +""" The core of the `CherryPy <http://www.cherrypy.org/>`_-powered +server. """ import sys import time @@ -12,10 +13,12 @@ from cherrypy.process.plugins import Daemonizer, DropPrivileges, PIDFile def on_error(*args, **kwargs): # pylint: disable=W0613 - """ define our own error handler that handles xmlrpclib.Fault + """ CherryPy error handler that handles :class:`xmlrpclib.Fault` objects and so allows for the possibility of returning proper - error codes. this obviates the need to use the builtin CherryPy - xmlrpc tool """ + error codes. This obviates the need to use + :func:`cherrypy.lib.xmlrpc.on_error`, the builtin CherryPy xmlrpc + tool, which does not handle xmlrpclib.Fault objects and returns + the same error code for every error.""" err = sys.exc_info()[1] if not isinstance(err, xmlrpclib.Fault): err = xmlrpclib.Fault(xmlrpclib.INTERNAL_ERROR, str(err)) @@ -25,8 +28,11 @@ cherrypy.tools.xmlrpc_error = ErrorTool(on_error) class Core(BaseCore): - """ The CherryPy-based server core """ + """ The CherryPy-based server core. """ + #: Base CherryPy config for this class. We enable the + #: ``xmlrpc_error`` tool created from :func:`on_error` and the + #: ``bcfg2_authn`` tool created from :func:`do_authn`. _cp_config = {'tools.xmlrpc_error.on': True, 'tools.bcfg2_authn.on': True} @@ -36,11 +42,15 @@ class Core(BaseCore): cherrypy.tools.bcfg2_authn = cherrypy.Tool('on_start_resource', self.do_authn) + #: List of exposed plugin RMI self.rmi = self._get_rmi() cherrypy.engine.subscribe('stop', self.shutdown) + __init__.__doc__ = BaseCore.__init__.__doc__.split('.. -----')[0] def do_authn(self): - """ perform authentication """ + """ Perform authentication by calling + :func:`Bcfg2.Server.Core.BaseCore.authenticate`. This is + implemented as a CherryPy tool.""" try: header = cherrypy.request.headers['Authorization'] except KeyError: @@ -60,10 +70,11 @@ class Core(BaseCore): @cherrypy.expose def default(self, *args, **params): # pylint: disable=W0613 - """ needed to make enough changes to the stock - XMLRPCController to support plugin.__rmi__ and prepending - client address that we just rewrote. it clearly wasn't - written with inheritance in mind :( """ + """ Handle all XML-RPC calls. It was necessary to make enough + changes to the stock CherryPy + :class:`cherrypy._cptools.XMLRPCController` to support plugin + RMI and prepending the client address that we just rewrote it. + It clearly wasn't written with inheritance in mind.""" rpcparams, rpcmethod = xmlrpcutil.process_body() if rpcmethod == 'ERRORMETHOD': raise Exception("Unknown error processing XML-RPC request body") @@ -92,12 +103,17 @@ class Core(BaseCore): return cherrypy.serving.response.body def _daemonize(self): + """ Drop privileges with + :class:`cherrypy.process.plugins.DropPrivileges`, daemonize + with :class:`cherrypy.process.plugins.Daemonizer`, and write a + PID file with :class:`cherrypy.process.plugins.PIDFile`. """ DropPrivileges(cherrypy.engine, uid=self.setup['daemon_uid'], gid=self.setup['daemon_gid']).subscribe() Daemonizer(cherrypy.engine).subscribe() PIDFile(cherrypy.engine, self.setup['daemon']).subscribe() def _run(self): + """ Start the server listening. """ hostname, port = urlparse(self.setup['location'])[1].split(':') if self.setup['listen_all']: hostname = '0.0.0.0' @@ -117,4 +133,9 @@ class Core(BaseCore): return True def _block(self): + """ Enter the blocking infinite server + loop. :func:`Bcfg2.Server.Core.BaseCore.shutdown` is called on + exit by a :meth:`subscription + <cherrypy.process.wspbus.Bus.subscribe>` on the top-level + CherryPy engine.""" cherrypy.engine.block() |