def main(): # Let's read the command line usage = "usage: %prog [options]" # add 'arg1 arg2' etc as required parser = FilteredOptionParser(usage=usage, version="Peloton version %s" % peloton.RELEASE_VERSION) # parser.set_defaults(nodetach=False) parser.add_option("--prefix", help="Prefix directory to help with setting paths") parser.add_option("--nodetach", action="store_true", default=False, help="Prevent PSC detaching from terminal [default: %default]") parser.add_option("-c", "--configfile", help="Path to configuration file for the PSC [default: %default].", dest="configfile", default='psc.pcfg') parser.add_option("-b", "--bind", help="""specify the host:port to which this instance should bind. Overides values set in configuration.""", dest='bindhost') parser.add_option("--anyport", action="store_true", default=False, help="""When set, this permits the PSC to seek a free port if the configured port is not available.""") parser.add_option("-s", "--servicepath", help="""Directory containing peloton services. You may specify several such directories with multiple instances of this option [default: %default]""", action="append", default=["$PREFIX/service"]) parser.add_option("--loglevel", help="""Set the logging level to one of critical, fatal, error(uat, prod), warning, info(test), debug(dev). (defaults for each run mode indicated in brackets).""", choices=['critical', 'fatal', 'error', 'warning', 'info', 'debug']) parser.add_option("--logdir", help="""Directory to which log files should be written. By setting this argument the logging-to-file system will be enabled.""") parser.add_option("--disable", help="""Comma delimited list of plugins to prevent starting even if configuration has them enabled""", action="append") parser.add_option("--enable", help="""Comma delimited list of plugins to start even if configuration has them disabled""", action="append") parser.add_option("--flags", help="""Comma delimited list of flags to add to this PSC.""", action="append") options, args = parser.parse_args() # Handling errors and pumping back through the system #if len(args) != 1: # parser.error("incorrect number of arguments") # add any sevice directories to sys.path if not already there for sd in options.servicepath: if sd not in sys.path: sys.path.append(sd) # enable, disable and flags are all 'append' types, but allow # comma delimited entries as well so we need to turn the list of # n potentially compound entries into a single list if options.enable: options.enable = deCompound(options.enable) else: options.enable=[] if options.disable: options.disable = deCompound(options.disable) else: options.disable=[] if options.flags: options.flags = deCompound(options.flags) else: options.flags=[] # determine the appropriate log-level for the root logger based # on supplied arguments. if options.loglevel: options.loglevel = options.loglevel.upper() else: options.loglevel = "ERROR" # Load configuration from file pc = PelotonSettings() try: pc.load(options.configfile) except IOError: sys.stderr.write("There is no profile for the PSC!\n") return 1 if pc.profile.has_key('flags'): pc.profile.flags.extend(options.flags) else: pc.profile.flags = options.flags # copy in the necessary from options to config keys =['configfile', 'nodetach', 'bindhost', 'anyport', 'servicepath', 'loglevel', 'logdir', 'enable', 'disable'] for k in keys: pc[k] = getattr(options, k) try: exitCode = start(pc) except: logging.getLogger().exception('Untrapped error in PSC: ') exitCode = 99 return exitCode
class PelotonService(object): """ Base class for all services. Public methods all have names prefixed 'public_', much as twisted spread remote callable methods are prefixed 'remote_'. Configuration ============= Services live in a strictly regimented structure on the file system. This simplifies auto-generation of code and automated loading with minimal magic. The root path points to a directory which contains the service directory. The service directory is laid out as follows, where the service is called FooBar:: service_root/foobar/config/service.pcfg /foobar.py /<supporting code> /resource/... Note that nomenclature is relatively simple; the service directory must be named the same as the service shortname (when lowercased). The configuration files are named '*.pcfg'. The service directory must contain at the very least a file called foobar.py (note lower case) containing the class FooBar(PelotonService,...). Here FooBar retains it's original capitalisation and, indeed, it is a matter of convention that the service name should be camel case. """ def __init__(self, name, dispatcher, logger): """ homePath passed in on construction because from this module cannot find where the concrete sub-class lives. Configurations are found relative to this homePath in homePath/config. """ self.name = name self.dispatcher = dispatcher self.logger = logger def initSupportServices(self): """ Start supporting services, such as the logger. Kept out of __init__ so that we can instantiate very lightly (as required by the launch sequencer.)""" self.logger = logging.getLogger(self.name) def loadConfig(self, servicePath, runconfig=None): """ Load service configuration file and then augment with details of all the methods in this service classed as 'public'. In doing this the attributes assigned by any decorators on the public methods are checked out, especially the transform chain. Defaults are assigned to, for example, HTML and XML output keys if none has been specified. Standard templates are sought out on the filesystem and attached where found and the @template keyword is substituted for in the transform chain. runconfig is the name of a run-time configuration file specified at the time of launching (either relative to the service config dir or an absolute path). This can specify many things including, for example, the name under which to publish this service and the location of the resource folder. Developers should not use the logger here: loadConfig should be useable prior to initSupportServices having been called.""" servicePath, self.settings = locateService(self.name, servicePath, runconfig=runconfig) if self.settings.has_key('profile'): self.profile = self.settings.profile else: self.profile = PelotonSettings() self.profile['_sysRunConfig'] = runconfig if not self.profile.has_key('publishedName'): self.profile['publishedName'] = self.name publicMethods = [m for m in dir(self) if m.startswith('public_') and callable(getattr(self, m))] if not self.profile.has_key('methods'): self.profile['methods'] = PelotonSettings() methods = self.profile['methods'] for nme in publicMethods: mthd = getattr(self, nme) shortname = nme[7:] templateTargets = findTemplateTargetsFor(self.profile['resourceRoot'], self.name, shortname) if hasattr(mthd, "_PELOTON_METHOD_PROPS"): properties = mthd._PELOTON_METHOD_PROPS else: properties = PelotonSettings() # step one, find all template files and insert # into transforms for target, templateFile in templateTargets: key = "transform.%s" % target if properties.has_key(key): # look for @template and substitute if "@template" in properties[key]: properties[key][properties[key].index("@template")] \ = "template('%s')" % templateFile else: # insert an entry properties['transform.%s'%target] = \ ["template('%s')" % templateFile] # step two insert defaults for any empty transforms that # need a little something defaultTransforms = {'xml' : 'defaultXMLTransform', 'html' : 'defaultHTMLTransform', 'json' : 'jsonTransform' } for target in ['xml', 'html', 'json']: key = "transform.%s" % target if not properties.has_key(key) \ or properties[key] == []: properties[key]=[defaultTransforms[target]] # step three, look for all transforms which still have # @transform in the chain and replace with defaults for k, v in properties.items(): if not k.startswith('transform.'): continue target = k[10:] if "@template" in v: try: v[v.index('@template')] = defaultTransforms[target] except: v[v.index('@template')] = '' if not methods.has_key(shortname): methods[shortname] = PelotonSettings() record = methods[shortname] record['doc'] = mthd.__doc__ record['properties'] = str(properties) self.version = self.profile['version'] def start(self): """ Executed after configuration is loaded, prior to starting work. Can be used to setup database pools etc. Overide in actual services. """ pass def stop(self): """ Executed prior to shuting down this service or the node. Can be used to cleanup database pools etc. Overide in actual services. """ pass def register(self, key, method, exchange='events', inThread=True): """ Registers method (which must have signature msg, exchange, key, ctag) to be the target for events on exchange with key matching the specified pattern. By default inThread is True which means the event handler will be called in a thread. If set False the event will be handled in the main event loop so care must be taken not to perform long-running operations in handlers that operate in this manner. The handler class is returned by this method; keeping a reference to it enables the service to de-register the handler subsequently.""" class ServiceMethodHandler(pb.Referenceable): def __init__(self, handler, inThread=True): self.handler = handler self.inThread = inThread def remote_eventReceived(self, msg, exchange, key, ctag): if self.inThread: reactor.callInThread(self.handler, msg, exchange, key, ctag) else: self.handler(msg, exchange, key, ctag) handler = ServiceMethodHandler(method, inThread) reactor.callFromThread(self.dispatcher.register, key, handler, exchange) return handler def deregister(self, handler): """De-register this event handler. """ reactor.callFromThread(self.dispatcher.deregister,handler) def fireEvent(self, key, exchange='events', **kwargs): """ Fire an event on the event bus. """ reactor.callFromThread( self.dispatcher.fireEvent, key, exchange, **kwargs) def public_index(self): """ Default index page for HTTP connections. """ return "There is no index for this service!"
def loadConfig(self, servicePath, runconfig=None): """ Load service configuration file and then augment with details of all the methods in this service classed as 'public'. In doing this the attributes assigned by any decorators on the public methods are checked out, especially the transform chain. Defaults are assigned to, for example, HTML and XML output keys if none has been specified. Standard templates are sought out on the filesystem and attached where found and the @template keyword is substituted for in the transform chain. runconfig is the name of a run-time configuration file specified at the time of launching (either relative to the service config dir or an absolute path). This can specify many things including, for example, the name under which to publish this service and the location of the resource folder. Developers should not use the logger here: loadConfig should be useable prior to initSupportServices having been called.""" servicePath, self.settings = locateService(self.name, servicePath, runconfig=runconfig) if self.settings.has_key('profile'): self.profile = self.settings.profile else: self.profile = PelotonSettings() self.profile['_sysRunConfig'] = runconfig if not self.profile.has_key('publishedName'): self.profile['publishedName'] = self.name publicMethods = [m for m in dir(self) if m.startswith('public_') and callable(getattr(self, m))] if not self.profile.has_key('methods'): self.profile['methods'] = PelotonSettings() methods = self.profile['methods'] for nme in publicMethods: mthd = getattr(self, nme) shortname = nme[7:] templateTargets = findTemplateTargetsFor(self.profile['resourceRoot'], self.name, shortname) if hasattr(mthd, "_PELOTON_METHOD_PROPS"): properties = mthd._PELOTON_METHOD_PROPS else: properties = PelotonSettings() # step one, find all template files and insert # into transforms for target, templateFile in templateTargets: key = "transform.%s" % target if properties.has_key(key): # look for @template and substitute if "@template" in properties[key]: properties[key][properties[key].index("@template")] \ = "template('%s')" % templateFile else: # insert an entry properties['transform.%s'%target] = \ ["template('%s')" % templateFile] # step two insert defaults for any empty transforms that # need a little something defaultTransforms = {'xml' : 'defaultXMLTransform', 'html' : 'defaultHTMLTransform', 'json' : 'jsonTransform' } for target in ['xml', 'html', 'json']: key = "transform.%s" % target if not properties.has_key(key) \ or properties[key] == []: properties[key]=[defaultTransforms[target]] # step three, look for all transforms which still have # @transform in the chain and replace with defaults for k, v in properties.items(): if not k.startswith('transform.'): continue target = k[10:] if "@template" in v: try: v[v.index('@template')] = defaultTransforms[target] except: v[v.index('@template')] = '' if not methods.has_key(shortname): methods[shortname] = PelotonSettings() record = methods[shortname] record['doc'] = mthd.__doc__ record['properties'] = str(properties) self.version = self.profile['version']
def __init__(self, *args, **kwargs): PelotonSettings.__init__(self, *args, **kwargs) self.__profiles = {} # holds transform chains as evaluated on the fly in coreio self.__outputTransforms = {}