Exemplo n.º 1
0
 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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
    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')
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
 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
Exemplo n.º 8
0
 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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
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
Exemplo n.º 11
0
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))