def add_apps_to_server(server_model, server_romeo): """add apps to server""" for app in apps: if not server_romeo.isRelated(app): continue log('%s should run %s' % (server_model.hostname, app.VALUE)) application = App(app.VALUE) application.runsOn(server_model)
def app(conversation, name): if App.exists(name): conversation.context['app'] = App(name) conversation.context['subject'] = App(name) conversation.say("Ok, we're talking about %s" % name) else: msg = "Sorry, I've never heard of the app \"%s\"." % name conversation.say(msg)
def getInstance(self, label): """get a reference to the application instance I attempt to delegate to the original provider. @param label: (string) @return AppInstance() @exception AssertionError """ label = str(label) if AppInstance.exists(Server(config.HOSTNAME), App(self.name), label): return AppInstance(Server(config.HOSTNAME), App(self.name), label) raise AssertionError('no such application instance')
def apps(conversation, mods): if 'server' not in conversation.context: all = [app.name for app in App.objects] conversation.say('Here are all the applications I know about.\n' + '\n'.join(all), useHTML=False) return server = conversation.context['server'] apps = sorted(app.name for app in server.apps) if mods: for mod in mods.split(): name = mod[1:] app = App.exists(name) and App(name) if not app: return conversation.say("Unknown app \"%s\"" % name) if mod.startswith('+'): app.runsOn(server) elif mod.startswith('-'): app.doesNotRunOn(server) else: return conversation.say("Invalid syntax.") conversation.say("Ok.") else: if apps: conversation.say('\n' + '\n'.join(apps), useHTML=False) else: conversation.say("%s has no matching applications" % server.hostname)
def performAction(conversation, apps, action, instanceFilter): result = None try: # Resolve the list of app names if apps == 'all': apps = set(app for app in App.objects if app.shouldRunOn) names = "" #used in question below else: givenNames = [name.strip() for name in apps.split(',')] apps = set() names = ', '.join(givenNames) for name in givenNames: if App.exists(name): apps.add( App(name) ) else: conversation.say("Sorry, I've never heard of the %s application" % name) raise AssertionError('Unknown Application') instances = [i for i in AppInstance.objects if i.app in apps if instanceFilter(i)] if not instances: conversation.say("There are no instances to %s." % action) raise AssertionError('No known instances') count = len(instances) question = 'Are you sure you want to %s all %d %s instances, <b>yes</b> or <b>no</b>?' % (action, count, names) answers = ('yes','no') try: d = conversation.ask(question, answers) wfd = defer.waitForDeferred(d) yield wfd response = wfd.getResult() except AlreadyAskingQuestion: conversation.say("This command requires confirmation but I have already asked you another question. " "Please answer that question first or tell me to <b>nevermind</b> it then try again.") raise AssertionError('already, busy asking question') if response == 'yes': #we also need to trap StopIteration(Exception) due to generators action_list = [ getattr(i, action)().addErrback(lambda x: \ x.trap(StopIteration) or x) for i in instances ] d = defer.DeferredList(action_list, consumeErrors=True) wfd = defer.waitForDeferred(d) yield wfd results = wfd.getResult() failures = [outcome for (successful,outcome) in results if not successful] conversation.context['failures'] = failures conversation.say("%s operations complete. %d instances failed to %s." % (action.capitalize(),len(failures),action)) else: conversation.say("Ok, I will not restart anything.") except AssertionError: pass except: result = Failure() yield result
def addInstance(self, label): """add a new application instance optionally changing the version I attempt to delegate to the original provider. @param label: (string) @return AppInstance() """ label = str(label) return AppInstance(Server(config.HOSTNAME), App(self.name), label)
def assimilate(selfs, appname, appversion, applabel): """attempt to manage an unmanaged process""" if self.running: ai = AppInstance(self.server, AppVersion(App(appname), appversion), applabel) if ai.running: return False #this instance is already running ai.updateInfo({'pid': self.process.pid}) if ai.running and ai.pid == self.process.pid: Event('scab-lost').fire(scab=self) return True return False
def construct(state): server = Server(state['hostname']) if state['connectFailure'] != server.connectFailure: server.connectFailure = state['connectFailure'] if state['debug'] != server.debug: server.debug = state['debug'] if 'installed' in state: for av, configs in state['installed'].items(): app, version = App(av[0]), av[1] av = AppVersion(app, version) server.installed[av] = set( ConfigPackage(*cp) for cp in configs) return server
def assimilateProcess(self, information): """Naive assimilation method. This should work for standard single instance applications, it will attempt to work with multi-instance applications as well. You should consider overriding this in an ApplicationPlugin if you need more advanced strategies. This is used by the ``services.application`` module to assimilate rogue application instances. This makes a best guess of which instance this should be assigned too. @param information (dict) "result of self.findPid" - required key "pid": (int) > 0 - optional key "name": (str) self.name == name - optional key "label": (str) bind to this instance - optional key "version": (str) set version or promote version NOTES ``information['label']`` if the instance is running already assimilation will fail @callback (instance of droned.models.app.AppInstance or None) @return (instance of defer.Deferred) """ result = None pid = information.get('pid', 0) #droned attempted to inject these environment variables into the app name = information.get( 'name', information.get('DRONED_APPLICATION', self.name)) version = information.get('version', information.get('DRONED_VERSION', None)) label = information.get('label', information.get('DRONED_LABEL', None)) try: assert pid #process is dead assert App.exists(name) #no such app if label: #appinstance label is known thisInst = self.getInstance(label) #may throw AssertionError assert not thisInst.running #make sure this instance isn't running if bool(version) and (thisInst.version != version): thisInst = self.setVersion(label, version) thisInst.pid = pid result = thisInst raise Exception('assimilated process') else: #make a best guess attempt options = set() for ai in App(name).localappinstances: if ai.running: continue options.add(ai) if bool(version): #try to perform a version match for opt in options: if opt.version == version: opt.pid = pid result = opt raise Exception('assimilated process') #last ditch effort, pick lowest free container thisInst = sorted([i for i in options if not i.running])[0] if bool(version) and (thisInst.version != version): thisInst = self.setVersion(thisInst.label, version) thisInst.pid = pid result = thisInst raise Exception('assimilated process') except: pass #swallow errors #minor cool down period d = defer.Deferred() self.reactor.callLater(0.1, d.callback, result) wfd = defer.waitForDeferred(d) yield wfd result = wfd.getResult() yield result
def performAction(conversation, apps, action, instanceFilter): result = None try: # Resolve the list of app names if apps == 'all': apps = set(app for app in App.objects if app.shouldRunOn) names = "" #used in question below else: givenNames = [name.strip() for name in apps.split(',')] apps = set() names = ', '.join(givenNames) for name in givenNames: if App.exists(name): apps.add(App(name)) else: conversation.say( "Sorry, I've never heard of the %s application" % name) raise AssertionError('Unknown Application') instances = [ i for i in AppInstance.objects if i.app in apps if instanceFilter(i) ] if not instances: conversation.say("There are no instances to %s." % action) raise AssertionError('No known instances') count = len(instances) question = 'Are you sure you want to %s all %d %s instances, <b>yes</b> or <b>no</b>?' % ( action, count, names) answers = ('yes', 'no') try: d = conversation.ask(question, answers) wfd = defer.waitForDeferred(d) yield wfd response = wfd.getResult() except AlreadyAskingQuestion: conversation.say( "This command requires confirmation but I have already asked you another question. " "Please answer that question first or tell me to <b>nevermind</b> it then try again." ) raise AssertionError('already, busy asking question') if response == 'yes': #we also need to trap StopIteration(Exception) due to generators action_list = [ getattr(i, action)().addErrback(lambda x: \ x.trap(StopIteration) or x) for i in instances ] d = defer.DeferredList(action_list, consumeErrors=True) wfd = defer.waitForDeferred(d) yield wfd results = wfd.getResult() failures = [ outcome for (successful, outcome) in results if not successful ] conversation.context['failures'] = failures conversation.say( "%s operations complete. %d instances failed to %s." % (action.capitalize(), len(failures), action)) else: conversation.say("Ok, I will not restart anything.") except AssertionError: pass except: result = Failure() yield result
class AppManager(Entity): """This is a generic application container service. It's sole purpose is to provide an abstraction to the application plugin. Think of this as an application service container. """ implements(IDroneModelAppManager) serializable = True #global container lock globalLock = defer.DeferredLock() running = property(lambda s: hasattr(s, '_task') and s._task.running) model = property(lambda s: IDroneDApplication(s)) #late plugin lookup action = property(lambda s: AdminAction(s.name)) invoke = property(lambda s: s.action.invoke) resultContext = property(lambda s: s.action.resultContext) exposedMethodInfo = property(lambda s: s.action.exposedMethodInfo) exposedMethods = property(lambda s: s.action.exposedMethods) instances = property(lambda s: App(s.name).localappinstances) labels = property(lambda s: (i.label for i in s.instances)) #whether or not the application service should discover apps for us discover = property(lambda s: not all([i.running for i in s.instances])) def __init__(self, name): self.name = name #this is for user defined storage self.applicationContext = {} #allow the models to block methods from registering self.blockedMethods = set() #create a local lock self.busy = defer.DeferredLock() #track events self.events = {} def log(self, message, label=None): """route logging messages to the application log and allow for custom labeling to be applied @param message: (string) @param label: (string) or (None) @return None """ info = self.name if label: info += ',%(label)s' % locals() logWithContext(type=info, route='application')(message) def __getstate__(self): """used to serialize the application model""" return { 'name': self.name, 'applicationContext': self.applicationContext } @staticmethod def construct(state): """rebuild the model with context @param state: (dict) return AppManger(state['name']) """ manager = AppManager(state['name']) manager.applicationContext = state['applicationContext'] return manager def start(self): """This is used by service binding to start""" if self.running: raise AssertionError('already running') #not only is this a safety, but makes sure the model is bound #donot ever remove this, otherwise first run won't automatically #create appinstances or any other models. #should be self, but make sure we avoid a race if self.model.service != AppManager(self.name): raise InvalidPlugin('Plugin for %s is invalid' % (self.name, )) self.action.log = self.log #override default logging #create default exposed methods, the model can override any of these self.expose('add', self.addInstance, ('instance', ), "Configure the specified instance", BUSYLOCK=True) self.expose('remove', self.removeInstance, ('instance', ), "Unconfigure the specified instance", BUSYLOCK=True) self.expose('start', self.startInstance, ('instance', ), "Start the instance", BUSYLOCK=True, INSTANCED=True) self.expose('stop', self.stopInstance, ('instance', ), "Stop the instance", BUSYLOCK=True, INSTANCED=True) self.expose('status', self.statusInstance, ('instance', ), "Status the instance", INSTANCED=True) self.expose('enable', self.enableInstance, ('instance', ), "Enable the instance", INSTANCED=True) self.expose('disable', self.disableInstance, ('instance', ), "Disable the instance", INSTANCED=True) self.expose('debug', self.debug, ('bool', ), "Turn application container debugging on or off") self.expose( 'labels', lambda: self.resultContext('\n'.join(sorted(self.labels)), None, ** {'labels': sorted(self.labels)}), (), "lists all application instance labels") #build our documentation self.rebuildHelpDoc() #check conditional events self._task = LoopingCall(self.conditionalEvents) self._task.start(1.0) def conditionalEvents(self): """check the status of conditional events""" if self.busy.locked: return #skip conditional event processing while busy for appevent in self.events.values(): if not appevent.condition: continue appevent.occurred() def registerEvent(self, name, callback, **kwargs): """Interface to Register Service Events""" #the self parameter will help ensure this event is unique to the service self.events[name] = ApplicationEvent(self.name, name, callback, **kwargs) def triggerEvent(self, name, data=None, delay=0.0): """Interface to trigger an out of band service event""" assert name in self.events, "No such event '%s'" % (name, ) return self.events[name].trigger(data, delay) def disableEvent(self, name): """Interface to disable a previously registered service event""" assert name in self.events, "No such event '%s'" % (name, ) self.events[name].event.disable() def enableEvent(self, name): """Interface to enable a previously disabled registered service event """ assert name in self.events, "No such event '%s'" % (name, ) self.events[name].event.enable() def stop(self): """This is used by service binding to stop""" try: if not self.running: raise AssertionError('not running') self._task.stop() #clear the event dictionary and delete events while self.events: name, appevent = self.events.popitem() if appevent.loop and appevent.loop.running: appevent.loop.stop() ApplicationEvent.delete(appevent) except: self.debugReport() #remove this appmanager's actions AdminAction.delete(self.action) ########################################################################### # This part of the class exposes the Model API to outside world ########################################################################### @synchronizedDeferred(globalLock) def unexpose(self, name, blacklist=True): """Removes an exposed method, probably not a good idea to expose""" #add method to blocked list if blacklist: self.blockedMethods.add(name) if name in self.exposedMethods: del self.exposedMethods[name] info = None found = False for info in self.exposedMethodInfo: (n, a, d) = info if n == name: found == True break if info and found: self.exposedMethodInfo.remove(info) def rebuildHelpDoc(self): """rebuild exposed method documentation""" self.action.buildDoc() @synchronizedDeferred(globalLock) def expose(self, name, method, args, doc, **kwargs): """Wraps the models exposed methods for gremlin and make methods available via blaster protocol for action invocation. expose(self, name, method, methodSignature, description, **kwargs) name: (string) - This is the action parameter to expose method: (callable) - This is the function name to call args: (tuple) - layout for parsing args description: (string) - Help Documentation to expose kwargs: INSTANCED: (bool) - sets the instanceOperator decorator for administrator's ease of use. BUSYLOCK: (bool) - sets the synchronizedDeferred decorator for this AppManager. GLOBALLOCK: (bool) - sets the synchronizedDeferred decorator for synchronizing all AppManagers. """ if name in self.blockedMethods: return #method was blocked by the model, probably #allow models to override the defaults and print a warning if name in self.exposedMethods: self.log('Warning method "%s" is already exposed' % (name, )) return #These decorators must be applied in a specific order of precedence requireInstance = kwargs.pop('INSTANCED', False) requireBusyLock = kwargs.pop('BUSYLOCK', False) requireGlobalLock = kwargs.pop('GLOBALLOCK', False) #applying decorators at runtime if requireBusyLock or requireGlobalLock or requireInstance: #ordering is critical if requireInstance: #this bizarre decorator is used b/c we need instance info. method = self.instanceOperation(method) if requireBusyLock: sync = synchronizedDeferred(self.busy) method = sync(method) if requireGlobalLock: sync = synchronizedDeferred(self.globalLock) method = sync(method) self.exposedMethodInfo.append((name, args, doc)) self.exposedMethods[name] = method ########################################################################### # This part of the class is for Generic actions that all apps perform ########################################################################### #FIXME is this really needed? def debug(self, var): """Enable or Disable application model debugging. You should extend this if you know how to enable application debugging in your custom 'application model'. returns deferred - already called """ #assume blaster which is string based, sent the message var = str(var) #for safety a = var.lower() context = {'code': 0} template = '[%(application)s] Debug ' try: if a == 'true': self.model.debug = True defer.setDebugging(True) template += 'Enabled' elif a == 'false': self.model.debug = False defer.setDebugging(False) template += 'Disabled' else: raise TypeError('input must be a bool, True/False') except Exception, exc: template += str(exc) context['code'] = 1 return defer.succeed(self.resultContext(template, None, **context))