# Copyright 2005 Divmod, Inc. See LICENSE file for details """ This module is no longer supported for use outside Vertex. """ from twisted.python import log from sets import Set from twisted.persisted import sob from twisted.application import service, internet from zope.interface import implements class Conf(dict): """A class to help in construction the configuration for delpoy(). Typical usage: from vertex.depserv import Conf conf = Conf() s = conf.section s('pop', port = 110, sslPort = 995) ... """ def section(self, name, **kw): self.setdefault(name, {}).update(kw) class NotPersistable: implements(sob.IPersistable) def __init__(self, original): self.original = original def setStyle(self, style): self.style = style def save(self, tag=None, filename=None, passphrase=None): pass class StartupError(Exception): pass class DependencyService(service.MultiService): """A MultiService that can start multiple services with interdependencies. Each keyword parameter is a dict which serves as the options for that service. Each service defines a method setup_SERVICE, which is called with the matching parameters (the service name must be all caps). If there is no key for SERVICE in the class parameters, the setup method is not called. The return value is ignored, and DependencyService makes no assumptions about any side effects. Each service may also optionally define depends_SERVICE which is called before the setup method with the same parameters as the setup method. This method returns a list of names of services on which SERVICE depends. DependencyService will then initialize the service is the correct order. If circular dependencies result, or a service depends on another service which does not exist or is not configured to run, StartupError is raised. The class can define required services by setting 'requiredServices' to a list of service names. These services will be initialized first in the order they appear in the list, ignoring all dependency information. If there are no parameters for a required service (consequently, the setup method would not normally be called), StartupError is raised. """ requiredServices = [] def __init__(self, **kw): service.MultiService.__init__(self) # this makes it possible for one service to change the configuration of # another. Avoid if possible, there if you need it. Be sure to properly # set the dependencies. self.config = kw self.servers = [] services = kw.keys() initedServices = Set() uninitedServices = Set(services) # build dependencies dependencies = {} for serv in services: try: dependMethod = self._getDependsMethod(serv) except AttributeError: continue dependencies[serv] = dependMethod(**kw[serv]) def initializeService(svc): self._getServiceMethod(svc)(**kw[svc]) initedServices.add(svc) uninitedServices.remove(svc) for svc in self.requiredServices: if dependencies.get(svc): raise StartupError( '%r is a required service but has unsatisfied ' 'dependency on %r' % (svc, dependencies[svc])) initializeService(svc) while uninitedServices: # iterate over the uninitialized services, adding those with no # outstanding dependencies to initThisRound. initThisRound = [] for serv in uninitedServices: for dep in dependencies.get(serv, []): if dep not in initedServices: if dep not in uninitedServices: raise StartupError( 'service %r depends on service %r, which is not ' 'configured or does not exist.' % (serv, dep)) break else: initThisRound.append(serv) if not initThisRound: raise StartupError( 'Can not initialize all services. Circular dependencies ' 'between setup methods?') for svc in initThisRound: initializeService(svc) def _getServiceMethod(self, service): return getattr(self, 'setup_%s' % (service.upper(),)) def _getDependsMethod(self, service): return getattr(self, 'depends_%s' % (service.upper(),)) def deploy(Class, name=None, uid=None, gid=None, **kw): """Create an application with the give name, uid, and gid. The application has one child service, an instance of Class configured based on the additional keyword arguments passed. The application is not persistable. """ svc = Class(**kw) if name is None: name = Class.__name__ # Make it easier (possible) to find this service by name later on svc.setName(name) app = service.Application(name, uid=uid, gid=gid) app.addComponent(NotPersistable(app), ignoreClass=True) svc.setServiceParent(app) return app deploy = classmethod(deploy) def attach(self, subservice): subservice.setServiceParent(self) return subservice def detach(self, subservice): subservice.disownServiceParent() def addServer(self, normalPort, sslPort, f, name): """Add a TCP and an SSL server. Name them `name` and `name`+'s'.""" tcp = internet.TCPServer(normalPort,f) tcp.setName(name) self.servers.append(tcp) if sslPort is not None: ssl = internet.SSLServer(sslPort, f, contextFactory=self.sslfac) ssl.setName(name+'s') self.servers.append(ssl) def discernPrivilegedServers(self): return [srv for srv in self.servers if srv.args[0] <= 1024] def discernUnprivilegedServers(self): return [srv for srv in self.servers if srv.args[0] > 1024] def privilegedStartService(self): for server in self.discernPrivilegedServers(): log.msg("privileged attach %r" % server) self.attach(server) return service.MultiService.privilegedStartService(self) def startService(self): for server in self.discernUnprivilegedServers(): log.msg("attaching %r" % server) self.attach(server) return service.MultiService.startService(self)