Esempio n. 1
0
class ChatRoom(Entity):
    """models the actions needed to interact in a chatroom"""
    implements(IDroneModelChatRoom)
    jabber = property(lambda s: services.getService('jabber'))
    jabber_config = property(lambda s: s.jabber.SERVICECONFIG)
    room = property(lambda s: s._room)
    jid = property(lambda s: '%s@%s' % \
            (s.room, s.jabber_config.JABBER_CHAT_SERVICE))
    nick = property(lambda s: s.jabber_config.JABBER_CHAT_NICK)

    def __init__(self, room):
        self._room = room
        self.conversation = Conversation(self.jid)
        self.conversation.groupChat = True

    def join(self):
        """join the chat room"""
        self.jabber.joinChatRoom(self.room)

    def leave(self):
        """leave the chat room"""
        self.jabber.leaveChatRoom(self.jid)

    def hear(self, message):
        """Only pay attention to messages that start with my chat nick"""
        if message.lower().split()[0] == self.nick:
            message = message[len(self.nick):].strip()
            self.conversation.hear(message)
Esempio n. 2
0
class AdaptToNullProcess(object):
    """I can adapt a L{IDroneModelAppProcess} provider or a 
       L{IDroneModelAppInstance} provider to a L{IKittNullProcess} provider.

       I hold no references to the Original Object after Instantiation.
    """
    implements(IKittNullProcess)

    def __init__(self, original):
        self.process = NullProcess()
        #make an attempt to update the original caller
        if hasattr(original, 'pid'):
            try:
                original.pid = self.process.pid
            except:
                pass
        if hasattr(original, 'inode'):
            try:
                original.inode = self.process.inode
            except:
                pass

    def __getattribute__(self, name):
        try:
            return object.__getattribute__(self, name)
        except:
            return self.process.__getattribute__(name)
Esempio n. 3
0
class ProcessSnapshot(Process):
    """Snapshot of process information"""
    implements(IKittProcessSnapshot)

    def __init__(self, *args):
        Process.__init__(self, *args)
        self.update()

    def update(self):
        """Update the snapshot"""
        stats = os.stat(self.path)
        self.uid = stats.st_uid
        self.gid = stats.st_gid
        self.cmdline = self.readFile('cmdline').split('\000')[:-1]
        try:
            self.cwd = os.readlink('%s/cwd' % self.path)
        except:
            self.cwd = None
        #        self.environ = self.getEnv()
        try:
            self.exe = os.readlink('%s/exe' % self.path)
        except:
            self.exe = None
        self.fd = self.getFD()
        self.tasks = self.getTasks()
        try:
            self.root = os.readlink('%s/root' % self.path)
        except:
            self.root = None
        self.tgid = int(
            self.readFile('status').split('Tgid:', 1)[1].split(None, 1)[0])
        vars(self).update(self.getStats())
Esempio n. 4
0
class AdaptToProcess(object):
    """I can adapt a L{IDroneModelAppProcess} provider or a 
       L{IDroneModelAppInstance} provider to a L{IKittProcess} provider.

       I hold no references to the Original Object after Instantiation.
    """
    implements(IKittProcess)

    def __init__(self, original):
        self.process = NullProcess()  #assume the process is dead
        if original.server.hostname == config.HOSTNAME:
            #delay scanning the process, for as long as possible
            try:
                self.process = ProcessSnapshot(original.pid)
            except InvalidProcess:
                pass  #raise all others
        else:
            self.process = RemoteProcess(original.pid)
        #make an attempt to update the original caller
        if hasattr(original, 'pid'):
            try:
                original.pid = self.process.pid
            except:
                pass
        if hasattr(original, 'inode'):
            try:
                original.inode = self.process.inode
            except:
                pass

    def __getattribute__(self, name):
        try:
            return object.__getattribute__(self, name)
        except:
            return self.process.__getattribute__(name)
Esempio n. 5
0
class RemoteProcess(NullProcess):
    """This is a remote process that looks like a live process"""
    implements(IKittRemoteProcess)
    pid = property(lambda s: s.info.get('pid', 0))
    inode = property(lambda s: s.info.get('inode', 0))
    ppid = property(lambda s: s.info.get('ppid', 0))
    memory = property(lambda s: s.info.get('memory', 0))
    fd_count = property(lambda s: s.info.get('fd_count', 0))
    stats = property(lambda s: s.info.get('stats', {}))
    threads = property(lambda s: s.info.get('threads', 0))
    exe = property(lambda s: s.info.get('exe', None))
    cmdline = property(lambda s: s.info.get('cmdline', []))
    environ = property(lambda s: s.info.get('environ', {}))
    def __init__(self, pid):
        self.info = {'pid': pid}
    def updateProcess(self, infoDict):
        self.info.update(infoDict)
    def isRunning(self): return bool(self.pid)
    def memUsage(self): return self.memory
    def getFD(self): return [ i for i in self.fd_count ]
    def getStats(self): return self.stats
    def getEnv(self): return self.environ
    def getTasks(self): return set([ i for i in self.threads ])
    def cpuUsage(self):
        return {
            'user_util': self.info.get('user_util', 0.0),
            'sys_util': self.info.get('sys_util', 0.0),
        }
    def __str__(self): return '%s(pid=%d)' % (self.__class__.__name__, self.pid)
    __repr__ = __str__
Esempio n. 6
0
class Process(object):
    """Represents a process
       A platform specific backend should be developed
       The purpose of this class is to demonstrate
       a skeleton for the implementation of IKittProcess.
    """
    implements(IKittProcess)

    #expected attributes
    running = property(lambda s: s.isRunning())
    inode = property(lambda s: 0)  #figure out how to get
    pid = property(lambda s: 0)  #figure out how to get
    ppid = property(lambda s: 0)  #figure out how to get
    exe = property(lambda s: None)  #figure out how to get
    cmdline = property(lambda s: [])  #figure out how to get
    memory = property(lambda s: s.memUsage())
    fd_count = property(lambda s: len(s.getFD()) or 3)
    stats = property(lambda s: s.getStats())
    environ = property(lambda s: s.getEnv())
    threads = property(lambda s: len(s.getTasks()))

    def __init__(self, pid):
        raise NotImplemented()

    def isRunning(self):
        return False

    def getEnv(self):
        return {}

    def getFD(self):
        return {}

    def getTasks(self):
        return {}

    def getStats(self):
        return {}

    def memUsage(self):
        return 0

    def cpuUsage(self):
        return {
            'user_util': 0.0,
            'sys_util': 0.0,
        }

    def __str__(self):
        return '%s(pid=%d)' % (self.__class__.__name__, self.pid)

    __repr__ = __str__
Esempio n. 7
0
class ProcessSnapshot(Process):
    """Snapshot of process information"""
    implements(IKittProcessSnapshot)

    def __init__(self, *args):
        Process.__init__(self, *args)
        self.update()

    def update(self):
        """Update the snapshot"""
        vars(self).update(self.getStats())
        self.cwd = os.readlink('%s/path/cwd' % self.path)
        self.environ = self.getEnv()
        self.exe = self.fname[0]
        self.fd = self.getFD()
        self.tasks = self.getTasks()
        self.root = os.readlink('%s/path/root' % self.path)
Esempio n. 8
0
class NullProcess(Process):
    """Represents a non-existant process"""
    #   Note: you should not need to override
    #     this implemenation, but you can if you
    #     want too
    implements(IKittNullProcess)
    pid = property(lambda s: 0)
    ppid = property(lambda s: 0)
    inode = property(lambda s: 0)
    exe = property(lambda s: None)
    cmdline = property(lambda s: [])
    fd_count = property(lambda s: 0)

    def __init__(self, pid=0):
        self._pid = 0

    def isRunning(self):
        return False

    def getEnv(self):
        return {}

    def getFD(self):
        return {}

    def getStats(self):
        return {}

    def getTasks(self):
        return {}

    def memUsage(self):
        return 0

    def cpuUsage(self):
        return {
            'user_util': 0.0,
            'sys_util': 0.0,
        }
Esempio n. 9
0
class RPMApplication(ApplicationPlugin):
    """I am a standard RPM Application Plugin.  I should work for standard
       rpm'd installations given you romeo config shortname is the same as
       the name of your rpm.
    """

    SEARCH_DELAY = 5.0  #override in romeo
    implements(IDroneDApplication)

    def __init__(self, *args, **kwargs):
        Event('instance-found').subscribe(self._check_version)

    @safe(None)
    def _check_version(self, occurrence):
        ai = occurrence.instance
        if ai.app.name == self.name:
            return self._updateVersion(None, ai.label)

    def _updateVersion(self, result, label):
        thisInst = self.getInstance(label)
        try:
            ts = rpm.TransactionSet()
            mi = ts.dbMatch('name', self.name)
            for h in mi:
                if not h: break
                version = AppVersion.makeVersion(self.name, h['version'])
                if version > thisInst.appversion:
                    thisInst.version = version
        except:
            err('exception while setting version')
        return result

    def startInstance(self, label):
        """I query the RPM DB and make sure the version is up to date"""
        d = ApplicationPlugin.startInstance(self, label)
        d.addCallback(self._updateVersion, label)
        return d  #return the deferable object
Esempio n. 10
0
class Action(Entity):
    implements(IDroneModelAction)
    completed = property(lambda self: self.deferred.called)
    succeeded = property(lambda self: self.completed and not \
            isinstance(self.outcome,Failure) )
    failed = property(lambda self: self.completed and not self.succeeded)
    stale = property(lambda self: self.finishedAt and \
            time.time() - self.finishedAt > config.ACTION_EXPIRATION_TIME)
    finishedAt = None
    outcome = None

    def __init__(self, description, deferred):
        self.description = description
        self.deferred = deferred
        self.startedAt = time.time()
        self.context = {}
        deferred.addBoth(self.__finish)

    def __finish(self, outcome):
        self.outcome = outcome
        self.finishedAt = time.time()
        config.reactor.callLater(config.ACTION_EXPIRATION_TIME, Action.delete,
                                 self)
        return outcome
Esempio n. 11
0
class ConversationContext(EntityContext):
    implements(IConversationContext)
    entityAttr = 'conversation'
    specialKeys = ['conversation', 'buddy', 'agent', 'issue', 'sop']

    def get_conversation(self):
        return self.conversation

    def get_buddy(self):
        return self.conversation.buddy

    def get_agent(self):
        if SupportAgent.exists(self.conversation.buddy):
            return SupportAgent(self.conversation.buddy)

    def get_issue(self):
        agent = self['agent']
        if agent:
            return agent.currentIssue

    def get_sop(self):
        agent = self['agent']
        if agent:
            return agent.sop
Esempio n. 12
0
class ProcessSnapshot(object):
    """Snapshot of process information"""
    implements(IKittProcessSnapshot)
    info = property(lambda s: s._data)
    pid = property(lambda s: s._pid)
    inode = property(lambda s: s.info.get('inode', 0))
    ppid = property(lambda s: s.info.get('ppid', 0))
    memory = property(lambda s: s.memUsage())
    fd_count = property(lambda s: len(s.getFD()) or 3)
    stats = property(lambda s: s.getStats())
    threads = property(lambda s: s.info.get('threads', 0))
    exe = property(lambda s: s.info.get('exe', None))
    environ = property(lambda s: s.getEnv())
    cmdline = property(lambda s: s.info.get('cmdline', []))
    running = property(lambda s: s.isRunning())
    uid = property(lambda s: s.info.get('uid', 0))
    gid = property(lambda s: s.info.get('gid', 0))

    def __del__(self):
        if self._task.running:
            self._task.stop()

    def __init__(self, pid):
        self.deferred = defer.succeed(None)
        self._lastupdate = 0
        self._pid = pid
        self._data = {}
        self._deferred = defer.succeed(None)
        self._task = task.LoopingCall(self._poll)
        try:
            LiveProcess(pid)  #warm the cache
            self._task.start(process_poll)
        except InvalidProcess:
            self.__class__.delete(self)

    def _poll(self):
        if self.deferred.called:
            self.deferred = self.update()
            self.deferred.addErrback(lambda x: None)

    def isRunning(self):
        try:
            return LiveProcess(self.pid).running
        except:  #destroy stats on death
            self.__class__.delete(self)
            self._data = {}
            return False

    @synchronizedDeferred(semaphore)
    @defer.deferredGenerator
    def update(self):
        result = {}
        try:
            if (time.time() - self._lastupdate) < process_poll * 2:
                result = self._data  #throttle scans
            else:
                self._lastupdate = time.time()
                self.deferred = self._get_updates()
                wfd = defer.waitForDeferred(self.deferred)
                yield wfd
                self._data.update(wfd.getResult())
                result = self._data
        except:
            if self._task.running:
                self._task.stop()
            self.__class__.delete(self)
            self._data = {}  #destroy stats on death
            result = Failure()
        yield result

    def getTasks(self):
        return self.info.get('getTasks', set())

    def getStats(self):
        return self.info.get('getStats', {})

    def getFD(self):
        return self.info.get('getFD', {})

    def getEnv(self):
        return self.info.get('getEnv', {})

    def memUsage(self):
        return self.info.get('memUsage', 0)

    def cpuUsage(self):
        return self.info.get('cpuUsage', {'user_util': 0.0, 'sys_util': 0.0})

    @deferredAsThread
    def _get_updates(self):
        """lots of io in here on most platforms"""
        #each look will keep the process honest
        return {
            'getTasks': LiveProcess(self.pid).getTasks(),
            'getStats': LiveProcess(self.pid).getStats(),
            'memUsage': LiveProcess(self.pid).memUsage(),
            'cpuUsage': LiveProcess(self.pid).cpuUsage(),
            'getFD': LiveProcess(self.pid).getFD(),
            'getEnv': LiveProcess(self.pid).getEnv(),
            'ppid': LiveProcess(self.pid).ppid,
            'cmdline': LiveProcess(self.pid).cmdline,
            'exe': LiveProcess(self.pid).exe,
            'uid': LiveProcess(self.pid).uid,
            'gid': LiveProcess(self.pid).gid,
            'inode': LiveProcess(self.pid).inode,
            'threads': LiveProcess(self.pid).threads
        }

    def __str__(self):
        return '%s(pid=%d)' % (self.__class__.__name__, self.pid)

    __repr__ = __str__
Esempio n. 13
0
class Server(Entity):
    implements(IDroneModelServer)
    connectFailure = None
    appinstances = property( lambda self: (i for i in AppInstance.objects if \
            i.server is self) )
    apps = property(lambda self:
                    (a for a in App.objects if self in a.shouldRunOn))
    scabs = property(lambda self:
                     (s for s in Scab.objects if s.server is self))
    unreachable = property(lambda self: self.connectFailure is not None)
    #FIXME this doesn't seem right we should remove it
    installedApps = property(lambda self: set(av.app for av in self.installed))
    debug = False
    serializable = True
    listed = False
    logs = {}

    def __init__(self, hostname):
        self.hostname = hostname
        self.installed = {}
        self.droned = DroneD(self)
        self.manager = ServerManager(self)

    def __getstate__(self):
        installed = {}
        for appversion, configs in self.installed.items():
            av = (appversion.app.name, appversion.version)
            installed[av] = []
            for configpkg in configs:
                cp = (configpkg.name, configpkg.version)
                installed[av].append(cp)

        return {
            'hostname': self.hostname,
            'connectFailure': self.connectFailure,
            'debug': self.debug,
            'installed': installed,
        }

    @staticmethod
    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

    @staticmethod
    def byName(name):
        if Server.exists(name):
            return Server(name)
        for server in Server.objects:
            if server.hostname.startswith(name):
                return server

    def startPolling(self):
        self.droned.startPolling()

    def stopPolling(self):
        self.droned.stopPolling()
Esempio n. 14
0
class ProcessSnapshot(Process):
    """Snapshot of process information"""
    implements(IKittProcessSnapshot)
    pass
Esempio n. 15
0
class AppVersion(Entity):
    """Track application versions"""
    implements(IDroneModelAppVersion)
    description = property( lambda self: "%s %s" % \
            (self.app.name, self.version_string) )
    serializable = True

    def __getattribute__(self, name):
        """Overrode to fulfill Interface Obligations"""
        if name in ('package','major','minor','micro','prerelease','base',\
                'short'):
            return self.version.__getattribute__(name)
        return object.__getattribute__(self, name)

    def __init__(self, *args, **kwargs):
        self.version = Version(*args, **kwargs)
        self.app = IDroneModelApp(self.version)

    @property
    def version_string(self):
        """makes a nice version string that we can use for reconstruction"""
        result = '.'.join([
            str(self.major),
            str(self.minor),
            str(self.micro),
        ])
        if self.prerelease:
            result += '.%s' % str(self.prerelease)
        return result

    def __getstate__(self):
        return {
            'app': self.package,
            'version': self.version_string,  #for proper serialization
        }

    def __cmp__(self, other):
        """overrode for easy comparison of AppVersion to AppVersion and Version
           to AppVersion.

           @raise IncomparableVersions:  when the package names of the versions
               differ.
           @param other L{twisted.python.versions.Version}, 
               L{IDroneModelAppVersion}, or object
           @return C{int} of value -1, 0, or 1
        """
        try:
            if IDroneModelAppVersion.providedBy(other):
                return self.version.__cmp__(other.version)
        except IncomparableVersions:
            raise
        except:
            pass
        if isinstance(other, Version):
            return self.version.__cmp__(other)
        return object.__cmp__(self, other)

    @staticmethod
    def makeAppVersion(name, version):
        """Similar to ``makeArgs``

           @return L{IDroneModelAppVersion} provider
        """
        args, kwargs = AppVersion.makeArgs(name, version)
        return AppVersion(*args, **kwargs)

    @staticmethod
    def makeVersion(name, version):
        """Similar to ``makeArgs``

           @return L{twisted.python.versions.Version}
        """
        args, kwargs = AppVersion.makeArgs(name, version)
        return Version(*args, **kwargs)

    @staticmethod
    def versionExists(name, version):
        """check if this L{IDroneModelAppVersion} provider exists"""
        args, kwargs = AppVersion.makeArgs(name, version)
        return AppVersion.exists(*args, **kwargs)

    @staticmethod
    def makeArgs(name, version):
        """goes through great lengths to make args for use
           by a constructor for class types -
               L{IDroneModelAppVersion} providers or 
               L{twisted.python.versions.Version}.

           @raises TypeError - if version is not convertable

           @param name C{str}
           @param version C{str}, C{list}, C{tuple}, or 
               L{twisted.python.versions.Version}

           @return ((name, major, minor, micro), {'prerelease': prerelease})
        """
        default = [0, 0, 0, 0]  #last number denotes pre-release
        if isinstance(version, Version):
            if version.package == name:
                return ((name, version.major, version.minor, version.micro), {
                    'prerelease': version.prerelease
                })
            else:
                version = []
        if isinstance(version, str):
            version = version.split('.')
        elif isinstance(version, tuple):
            version = list(version)
        elif isinstance(version, type(None)):
            version = []
        else:
            raise TypeError('Unacceptable version input %s' % type(version))
        version = version + default  #pad the length for comparisons
        v = [i[0] for i in zip(version, default)]
        kwargs = {'prerelease': None}
        try:  #don't try too hard to get this right
            if len(v) == 4 and v[3]:
                kwargs['prerelease'] = int(v[3])
        except:
            pass
        return (tuple((name, ) + tuple([int(i) for i in v[0:3]])), kwargs)

    @staticmethod
    def construct(state):
        name = state['app']
        appversion = AppVersion.makeAppVersion(name, state['version'])
        return appversion
Esempio n. 16
0
class JabberClient(object):
    implements(IDroneDService)  #requirement
    xmlstream = None
    connected = property(lambda self: self.xmlstream is not None)

    parentService = None  #interface required attribute
    service = None  #interface required attribute
    SERVICENAME = 'jabber'  #interface required attribute

    #this should be overrode in romeo
    SERVICECONFIG = dictwrapper({
        'JABBER_CHAT_NICK':
        config.HOSTNAME,
        'JABBER_USERNAME':
        '******',
        'JABBER_PASSWORD':
        '******',
        'JABBER_SERVER':
        'jabber.example.net',
        'JABBER_CHAT_SERVICE':
        'conference.jabber.example.net',
        'JABBER_PORT':
        5222,
        'JABBER_RESOURCE':
        config.HOSTNAME,
        'JABBER_BROADCAST_INTERVAL':
        300,
        'JABBER_VALIDATE_XML':
        True,
        'JABBER_TRUST_ROOM':
        False,
        'DEPUTY':
        '*****@*****.**',
        'CONVERSATION_RESPONSE_PERIOD':
        180,
        'JABBER_JOIN_CHATROOM':
        False,
        'JABBER_TEAM_ROSTER':
        os.path.join(config.DRONED_HOMEDIR, 'teams'),
    })  #interface required attribute

    def running(self):
        """interface requirement"""
        return bool(self.service) and self.service.running

    def install(self, _parentService):
        """interface requirement"""
        self.parentService = _parentService
        user = decrypt(self.SERVICECONFIG.JABBER_USERNAME)
        server = self.SERVICECONFIG.JABBER_SERVER
        resource = self.SERVICECONFIG.JABBER_RESOURCE
        self.jid = JID("%(user)s@%(server)s/%(resource)s" % locals())
        self.broadcastTask = LoopingCall(self.broadcastPresence)
        self.sendQueue = []
        self.authenticated = False
        #load all jabber responders, after configuration
        import droned.responders
        droned.responders.loadAll()

    def start(self):
        """interface requirement"""
        if self.running(): return
        self.factory = XMPPClientFactory(
            self.jid, decrypt(self.SERVICECONFIG.JABBER_PASSWORD))
        self.factory.addBootstrap(STREAM_CONNECTED_EVENT, self.connectionMade)
        self.factory.addBootstrap(STREAM_END_EVENT, self.connectionLost)
        self.factory.addBootstrap(STREAM_AUTHD_EVENT,
                                  self.connectionAuthenticated)
        self.factory.addBootstrap(STREAM_ERROR_EVENT, self.receivedError)
        self.factory.addBootstrap(INIT_FAILED_EVENT, self.initFailed)
        self.service = TCPClient(self.SERVICECONFIG.JABBER_SERVER,
                                 self.SERVICECONFIG.JABBER_PORT, self.factory)
        self.service.setServiceParent(self.parentService)
        #build/rebuild jabber teams
        if not os.path.exists(self.SERVICECONFIG.JABBER_TEAM_ROSTER):
            try:
                os.makedirs(self.SERVICECONFIG.JABBER_TEAM_ROSTER)
            except:
                log('Cannot load team rosters because %s does not exits' % \
                        self.SERVICECONFIG.JABBER_TEAM_ROSTER)
                return
        for name in os.listdir(self.SERVICECONFIG.JABBER_TEAM_ROSTER):
            f = (self.SERVICECONFIG.JABBER_TEAM_ROSTER, name)
            if os.path.isfile('%s/%s' % f):
                Team(name)  #preload team rosters

    def stop(self):
        """interface requirement"""
        if self.service:
            self.factory.stopTrying()
            self.factory.stopFactory()
            self.service.disownServiceParent()
            self.service.stopService()
            self.service = None

    def connectionMade(self, xmlstream):
        log('connection made')
        self.xmlstream = xmlstream

    def connectionLost(self, xmlstream):
        log('connection lost')
        self.authenticated = False
        if self.broadcastTask.running:
            self.broadcastTask.stop()
        if self.connected:
            Event('jabber-offline').fire()
        self.xmlstream = None

    def connectionAuthenticated(self, xmlstream):
        log('connection authenticated')
        self.authenticated = True
        if not self.broadcastTask.running:
            self.broadcastTask.start(
                self.SERVICECONFIG.JABBER_BROADCAST_INTERVAL)

        xmlstream.addObserver('/message', self.receivedMessage)
        xmlstream.addObserver('/presence', self.receivedPresence)
        xmlstream.addObserver('/iq', self.receivedIQ)
        xmlstream.addObserver('/error', self.receivedError)
        Event('jabber-online').fire()
        while self.sendQueue:
            self.xmlstream.send(self.sendQueue.pop(0))

    def broadcastPresence(self):
        presence = Element(('jabber:client', 'presence'))
        #log('sending presence broadcast')
        self.xmlstream.send(presence)

    def sendMessage(self, to, body, useHTML=True, groupChat=False):
        message = Element(('jabber:client', 'message'))
        message['to'] = to
        message['type'] = (groupChat and 'groupchat') or 'chat'
        message.addElement('body', None, body)
        if useHTML:
            html = message.addElement('html',
                                      'http://jabber.org/protocol/xhtml-im')
            htmlBody = html.addElement('body', 'http://www.w3.org/1999/xhtml')
            htmlBody.addRawXml(unicode(body))
            if self.SERVICECONFIG.JABBER_VALIDATE_XML:
                validateXml(html.toXml())
        #safeXml = filter(lambda char: ord(char) < 128, message.toXml())
        #log('sending message: %s' % safeXml)
        log('sending message to %s: %s' % (to, body))
        if self.authenticated:
            self.xmlstream.send(message)
        else:
            log("not connected, queueing message", warning=True)
            self.sendQueue.append(message)

    def requestAuthorization(self, to):
        request = Element((None, 'iq'))
        request['type'] = 'set'
        request['id'] = 'auth-request:%s' % to
        query = Element((None, 'query'))
        query['xmlns'] = 'jabber:iq:roster'
        item = Element((None, 'item'))
        item['jid'] = to
        item['name'] = to.split('@')[0]
        query.addChild(item)
        request.addChild(query)
        log('sending auth request: %s' % request.toXml())
        self.xmlstream.send(request)

    def joinChatRoom(self, room):
        presence = Element((None, 'presence'))
        presence['from'] = self.jid.userhost()
        jid = '%s@%s/%s' % (room, self.SERVICECONFIG.JABBER_CHAT_SERVICE,
                            self.SERVICECONFIG.JABBER_CHAT_NICK)
        presence['to'] = jid
        x = Element(('http://jabber.org/protocol/muc', 'x'))
        history = Element((None, 'history'))
        history['maxchars'] = '0'
        x.addChild(history)
        presence.addChild(x)
        log('sending join: %s' % presence.toXml())
        self.xmlstream.send(presence)

    def leaveChatRoom(self, jid):
        if '/' not in jid:
            jid += '/' + self.SERVICECONFIG.JABBER_CHAT_NICK
        presence = Element((None, 'presence'))
        presence['from'] = self.jid.userhost()
        presence['to'] = jid
        presence['type'] = 'unavailable'
        log('sending leave: %s' % presence.toXml())
        self.xmlstream.send(presence)

    def receivedMessage(self, e):
        # Extract the body of the message
        try:
            message = str([c for c in e.children if c.name == 'body'][0])
        except:
            log('discarding invalid message (has no body!): %s' % e.toXml())
            return
        # Discard delayed messages
        delays = [x for x in e.children if x.name == 'delay']
        stamps = [ x for x in e.children \
               if x.name == 'x' and \
               x.compareAttribute('xmlns','jabber:x:delay') and \
               x.hasAttribute('stamp') ]
        #stampstring = str( stamps[0].getAttribute('stamp') )
        #timestamp = time.mktime( time.strptime(stampstring, "%Y%m%dT%H:%M:%S") )
        if delays or stamps:
            log('discarding delayed message: %s' % e.toXml())
            return

        # Route message to the right Conversation or ChatRoom entity
        if e.getAttribute('type') == 'chat':
            buddy = str(e['from'].split('/')[0])
            if not Conversation.exists(buddy):
                self.requestAuthorization(buddy)
            log('received message from %s: %s' %
                (buddy.split('@')[0], message))
            Conversation(buddy).hear(message)
        elif e.getAttribute('type') == 'groupchat':
            room = e['from'].split('@')[0]
            log('received message [chatroom=%s]: %s' % (room, message))
            ChatRoom(room).hear(message)
        else:
            log('received message of unknown type: %s' % e.toXml(), error=True)

    def receivedPresence(self, e):
        log('received presence: %s' % e.toXml())
        if e.getAttribute('type') == 'subscribe':
            log('received authorization request from %s' % e['from'])
            response = Element(('', 'presence'))
            response['to'] = e['from']
            response['type'] = 'subscribed'
            log('sending auth response: %s' % response.toXml())
            self.xmlstream.send(response)
            buddy = str(e['from'])
            if not Conversation.exists(buddy):
                self.requestAuthorization(buddy)
        elif e.getAttribute('type') == 'unavailable':
            #fix for openfire jabber server randomly kicking clients out and prevent kicks
            CHAT = '@%s/%s' % (self.SERVICECONFIG.JABBER_CHAT_SERVICE,
                               self.SERVICECONFIG.JABBER_CHAT_NICK)
            if e['to'] == self.jid.full() and e['from'].endswith(CHAT) and \
                    "status code='307'" in e.toXml():
                try:
                    log('%s has kicked me' % (e['from'], ))
                    self.joinChatRoom(e['from'].split(CHAT)[0])
                    log('successfully rejoined room')
                except:
                    err('Failed to recover from /kick')
            #elif any(1 for c in e.children if c.name == 'x'):
            #TODO detect buddies that go offline
            #    if we have a Conversation then unsubscribe .notify from all events

    def receivedIQ(self, e):
        log('received iq: %s' % e.toXml())

    def receivedError(self, f):
        log('received error: %s' % str(f))

    def initFailed(self, failure):
        log('Failed to initialize jabber connection:\n%s' %
            failure.getTraceback())
        self.stop()
        if failure.check(SASLAuthError):
            log('Will attempt to reconnect in 15 seconds...')
            config.reactor.callLater(15, self.start)
Esempio n. 17
0
class LiveProcess(Process):
    """Get realtime access to process information"""
    implements(IKittLiveProcess)
    pass
Esempio n. 18
0
class LiveProcess(Process):
    """Get realtime access to process information"""
    implements(IKittLiveProcess)

    def __init__(self, pid, fast=False):
        """Represents the /proc entries for a process, values are read 
           each time you access an attribute.
        """
        Process.__init__(self, pid)
        #Creates dummy attribs for user friendliness unless fast=True is specified
        if not fast:
            other_attribs = ('cmdline', 'cwd', 'environ', 'exe', 'fd', 'root',
                             'tgid')
            dummyAttrs = dict([(a,self.__getattribute__(a)) for a in \
                    stat_attribs + other_attribs])
            vars(self).update(dummyAttrs)
        self.cpuTime = self.__cpuSnapShot()

    def __times(self):
        """Returns seconds of system and user times"""
        return (float(self.utime) / JIFFIES_PER_SECOND,
                float(self.stime) / JIFFIES_PER_SECOND)

    def cpuUsage(self):
        """Returns a dictionary of system and user cpu utilization in terms 
           of percentage used
        """
        baseline = self.cpuTime
        self.cpuTime = self.__cpuSnapShot()
        u = (self.cpuTime[0] - baseline[0]) / (self.cpuTime[2] - baseline[2])
        s = (self.cpuTime[1] - baseline[1]) / (self.cpuTime[2] - baseline[2])
        return {'user_util': 100 * u, 'sys_util': 100 * s}

    def __cpuSnapShot(self):
        return self.__times() + tuple([cpuTotalTime()])

    def __getattribute__(self, attr):
        if attr == 'cmdline':
            return self.readFile('cmdline').split('\000')[:-1]
        elif attr == 'cwd':
            try:
                return os.readlink('%s/cwd' % self.path)
            except:
                return None
        elif attr == 'environ':
            return self.getEnv()
        elif attr == 'exe':
            try:
                return os.readlink('%s/exe' % self.path)
            except:
                return None
        elif attr == 'fd':
            return self.getFD()
        elif attr == 'tasks':
            return self.getTasks()
        elif attr == 'root':
            try:
                return os.readlink('%s/root' % self.path)
            except:
                return None
        elif attr == 'tgid':
            return int(
                self.readFile('status').split('Tgid:', 1)[1].split(None, 1)[0])
        elif attr in stat_attribs:
            return self.getStats()[attr]
        elif attr == 'uid':
            try:
                return os.stat(self.path).st_uid
            except:
                return None
        elif attr == 'gid':
            try:
                return os.stat(self.path).st_gid
            except:
                return None
        else:
            return object.__getattribute__(self, attr)
Esempio n. 19
0
class LiveProcess(Process):
    """Get realtime access to process information"""
    implements(IKittLiveProcess)

    def __init__(self, pid, fast=False):
        Process.__init__(self, pid)
        #Creates dummy attribs for user friendliness unless fast=True is specified
        if not fast:
            other_attribs = ('cwd', 'environ', 'exe', 'fd', 'root', 'tasks')
            dummyAttrs = dict([(a,self.__getattribute__(a)) for a in \
                    stat_attribs + other_attribs])
            vars(self).update(dummyAttrs)
        self.cpuTime = self.__cpuSnapShot()

    def __times(self):
        """Returns seconds of system and user times"""
        return (self.utime, self.stime)

    def cpuUsage(self):
        """Returns a dictionary of system and user cpu utilization in terms 
           of percentage used
           @return (dict)
        """
        baseline = self.cpuTime
        self.cpuTime = self.__cpuSnapShot()
        u = (self.cpuTime[0] - baseline[0]) / (self.cpuTime[2] - baseline[2])
        s = (self.cpuTime[1] - baseline[1]) / (self.cpuTime[2] - baseline[2])
        return {
            'user_util': 100 * u,
            'sys_util': 100 * s,
        }

    def __cpuSnapShot(self):  #FIXME probably, not tested enough
        return self.__times() + tuple([(time.time() - self.time)])

    def __getattribute__(self, attr):
        if attr == 'cwd':
            try:
                return os.readlink('%s/cwd' % self.path)
            except:
                return None
        elif attr == 'environ':
            return self.getEnv()
        elif attr == 'exe':
            self.fname[0]
        elif attr == 'fd':
            return self.getFD()
        elif attr == 'tasks':
            return self.getTasks()
        elif attr == 'cwd':
            try:
                return os.readlink('%s/path/cwd' % self.path)
            except:
                return None
        elif attr == 'root':
            try:
                return os.readlink('%s/path/root' % self.path)
            except:
                return None
        elif attr in stat_attribs:
            try:
                return self.getStats()[attr]
            except:
                pass
        else:
            return object.__getattribute__(self, attr)
Esempio n. 20
0
class EntityContext(object):
    """Abstract base class for representing context about an Entity with
       read-only metadata transparently mixed in as "special keys".
       Subclasses must override entityAttr and specialKeys attributes and
       for each specialKey a get_<key> method must be implemented.
    """

    implements(IEntityContext)
    entityAttr = None
    specialKeys = []

    def __init__(self, entity):
        #Verify that the subclass conforms to the API
        assert self.entityAttr is not None
        for key in self.specialKeys:
            assert hasattr(self, 'get_' + key)

        setattr(self, self.entityAttr, entity)
        self.data = {}

    def __getitem__(self, key):
        if key in self.specialKeys:
            accessor = getattr(self, 'get_' + key)
            return accessor()

        if key in self.data:
            return self.data[key]
        else:
            entity = getattr(self, self.entityAttr)
            raise KeyError("\"%s\" is not in the %s context" % (key, entity))

    def __setitem__(self, key, value):
        if key in self.specialKeys:
            raise KeyError("Cannot override special key \"%s\"" % key)
        self.data[key] = value

    def __delitem__(self, key):
        if key in self.specialKeys:
            raise KeyError("Cannot delete special key \"%s\"" % key)
        del self.data[key]

    def __contains__(self, key):
        return key in self.data or key in self.specialKeys

    def __iter__(self):
        for key in self.specialKeys:
            yield key
        for key in self.data:
            yield key

    def get(self, key, default=noDefault):
        try:
            return self[key]
        except KeyError:
            if default == noDefault:
                raise
            else:
                return default

    def pop(self, key, default=noDefault):
        try:
            return self.data.pop(key)
        except KeyError:
            if default == noDefault:
                raise
            else:
                return default

    def keys(self):
        return list(self)

    def values(self):
        return [self[key] for key in self]

    def items(self):
        return [(key, self[key]) for key in self]

    def update(self, otherDict):
        return self.data.update(otherDict)

    def clear(self):
        return self.data.clear()

    def copy(self):
        return dict(self.items())

    def __repr__(self):
        return repr(self.copy())
Esempio n. 21
0
class AppInstance(Entity):
    """Track application instances"""
    implements(IDroneModelAppInstance)
    crashed = property(lambda s: s.shouldBeRunning and not s.running)
    startupInstallInfo = {}
    runningConfigs = set()
    serializable = True
    state = property(lambda s: (s.crashed and 'crashed') or \
            (s.running and 'up') or 'not running')
    description = property(lambda self: "%s %s [%s] on %s" % \
            (self.app.name, self.version, self.label, self.server.hostname))
    localInstall = property(lambda s: bool(s.server.hostname == \
            config.HOSTNAME))
    #FIXME broken
    cpu = property(lambda s: 0.0)
    #immutable objects passed to the constructor
    label = property(lambda s: s._label, lambda x: None, lambda y: None,
                     'instance label')
    app = property(lambda s: s._app, lambda x: None, lambda y: None,
                   'L{IDroneModelApp} provider')
    server = property(lambda s: s._server, lambda x: None, lambda y: None,
                      'L{IDroneModelServer} provider')

    def __getattribute__(self, name):
        """Overrode to fulfill our interface obligations"""
        if name in ('running','ppid','memory','fd_count','stats','threads',\
                'exe','environ','cmdline'):
            return self.process.__getattribute__(name)
        return object.__getattribute__(self, name)

    def __init__(self, server, app, label):
        try:
            if not IDroneModelServer.providedBy(server):
                e = '%s is not a L{IDroneModelServer} provider' % str(server)
                raise AssertionError(e)
            if not IDroneModelApp.providedBy(app):
                e = '%s is not a L{IDroneModelAppVersion} provider' % \
                        str(appversion)
                raise AssertionError(e)
        except AssertionError:
            AppInstance.delete(self)
            raise

        #internal information
        self.shouldBeRunning = False

        #model information
        self._label = label
        self._app = IDroneModelApp(app)
        self._server = IDroneModelServer(server)

        #serializable information
        self.info = {}
        #volitile data, unserializable
        self.context = {}

    @property
    def children(self):
        """allow us to track an AppInstance's child processes"""
        if IDroneModelAppProcess.providedBy(self.process):
            return self.process.children  #generator
        return (i for i in [])  #empty generator

    @property
    def process(self):
        """The process object contained by this application instance

           @raise InvalidProcess
           @return (instance of AppProcess)
        """
        if not hasattr(self, '_process'):
            #try to grab a live process, on exception grab a NullProcess
            try:
                assert self.pid  #save the scan attempt
                self._process = IDroneModelAppProcess(self)
            except:
                self._process = IKittNullProcess(
                    self)  #should be a NullProcess
        elif IDroneModelAppProcess.providedBy(self._process) and not \
                AppProcess.isValid(self._process):
            self._process = IKittNullProcess(self)
        #keep the journal up to date
        self.info.update({
            'pid': self._process.pid,
            'inode': self._process.inode,
        })
        return self._process

    def __getstate__(self):
        state = {
            'server': self.server.hostname,
            'app': self.app.name,
            #avoid journal races
            'pid': self.info.get('pid', 0),
            'inode': self.info.get('inode', 0),
            'version': self.version,  #for proper serialization
            'label': self.label,
            'shouldBeRunning': self.shouldBeRunning,
            'enabled': self.enabled,
            'running': self.running,
            'info': {},  #other information storage
        }

        for attr, val in self.info.items():
            if attr in ('pid', 'inode', 'enabled'): continue
            state['info'][attr] = val
        return state

    @staticmethod
    def construct(state):
        server = Server(state['server'])
        #we need to format the version correctly
        appname = state['app']
        version = AppVersion.makeAppVersion(appname, state['version'])
        appinstance = AppInstance(server, App(appname), state['label'])
        appinstance.appversion = version
        pid = state.get('pid', 0)
        inode = state.get('inode', 0)
        from kitt.proc import isRunning
        if pid and isRunning(pid):
            appinstance.info.update({'pid': pid, 'inode': inode})
            process = AppProcess(server, pid)
            if process.inode == inode:
                setattr(appinstance, '_process', process)
        appinstance.updateInfo(state['info'])
        appinstance.enabled = state.get('enabled', False)
        appinstance.shouldBeRunning = state.get('shouldBeRunning', False)
        x = appinstance.running  #preload the process information
        #attempt to get our instance into the last known state
        return appinstance

    def updateInfo(self, info):
        """Called by app managers after a start/stop condition"""
        result = info
        if isinstance(info, Failure):
            info = info.check(DroneCommandFailed)
            if info: info = info.resultContext
        if not isinstance(info, dict):
            return result
        #don't allow dumb changes
        info.pop('pid', None)
        info.pop('inode', None)
        self.info.update(dict(**info))
        return result

    @defer.deferredGenerator
    def start(self):
        """convenient start method for an Application Instance"""
        result = None
        app = self.app.name
        label = self.label
        d = self.server.manager.run("%(app)s start %(label)s" % locals())
        wfd = defer.waitForDeferred(d)
        yield wfd
        result = wfd.getResult().values()[0]
        self.shouldBeRunning = True
        if self.server.hostname != config.HOSTNAME:
            self.updateInfo(result)
        if not self.running:
            result = Failure(DroneCommandFailed(result))
        yield result

    @defer.deferredGenerator
    def stop(self):
        """convenient stop method for an Application Instance"""
        result = None
        app = self.app.name
        label = self.label
        d = self.server.manager.run("%(app)s stop %(label)s" % locals())
        wfd = defer.waitForDeferred(d)
        yield wfd
        result = wfd.getResult().values()[0]
        self.shouldBeRunning = False
        if self.server.hostname != config.HOSTNAME:
            self.updateInfo(result)
        if self.running:
            result = Failure(DroneCommandFailed(result))
        yield result

    @defer.deferredGenerator
    def restart(self):
        """convenient restart method for an Application Instance"""
        result = None
        try:
            if self.running:
                d = self.stop()
                wfd = defer.waitForDeferred(d)
                yield wfd
                wfd.getResult()
            d = self.start()
            wfd = defer.waitForDeferred(d)
            yield wfd
            result = wfd.getResult()
        except:
            result = Failure()
            log('Unhandled exception\n' + result.getTraceback())
        yield result

    ###########################################################################
    # getting and setting of attr's ``pid``, ``inode``, ``version``, and
    # ``appversion`` are done below here.
    ###########################################################################
    def _getpid(self):
        pid = int(self.info.get('pid', 0))
        if hasattr(self, '_process') and (self.process.pid != pid):
            delattr(self, '_process')  #force rescan for process
            pid = self.info['pid'] = 0
        return pid

    def _getinode(self):
        inode = int(self.info.get('inode', 0))
        if hasattr(self, '_process') and (self.process.inode != inode):
            delattr(self, '_process')  #force rescan for process
            inode = self.info['inode'] = 0
        return inode

    def _setpid(self, pid):
        pid = int(pid)
        if hasattr(self, '_process') and (self.process.pid != pid):
            delattr(self, '_process')  #force update on change
        self.info['pid'] = pid
        return self.info['pid']

    def _setinode(self, inode):
        inode = int(inode)
        if hasattr(self, '_process') and (self.process.inode != inode):
            delattr(self, '_process')  #force update on change
        self.info['inode'] = inode
        return self.info['inode']

    def _getversion(self):
        if not hasattr(self, '_version'):
            self._version = AppVersion.makeAppVersion(self.app.name, None)
        return self.appversion.version_string

    def _getappversion(self):
        if not hasattr(self, '_version'):
            self._version = AppVersion.makeAppVersion(self.app.name, None)
        return IDroneModelAppVersion(self._version)

    def _setversion(self, version):
        """sets the self.appversion and self.version"""
        checkVersion = hasattr(self, '_version')  #could be reconstructing
        if checkVersion:
            checkVersion = self._getappversion()
        if IDroneModelAppVersion.providedBy(version):
            self._version = IDroneModelAppVersion(version)
        else:
            self._version = IDroneModelAppVersion(
                AppVersion.makeAppVersion(self.app.name, version))
        if checkVersion:
            data = {
                'instance': self,
                'version': self._version,
                'previous': checkVersion
            }
            if checkVersion < self._version:
                if checkVersion.major < self._version.major:
                    Event('new-major-release').fire(**data)
                else:
                    Event('new-release-version').fire(**data)
                return  #done
            Event('release-change').fire(**data)

    def _getenabled(self):
        return self.info.get('enabled', False)

    def _setenabled(self, enabled):
        enabled = bool(enabled)
        status = self._getenabled()
        self.info['enabled'] = enabled
        if (enabled != status) and enabled:
            Event('instance-enabled').fire(instance=self)
        elif (enabled != status) and not enabled:
            Event('instance-disabled').fire(instance=self)

    #dynamically changing properties that are special to the system
    pid = property(_getpid, _setpid, lambda x: None, 'process id')
    inode = property(_getinode, _setinode, lambda x: None, 'process inode')
    version = property(_getversion, _setversion, lambda x: None,
                       'App Version String')
    appversion = property(_getappversion, _setversion, lambda x: None,
                          'L{IDroneModelAppVersion} provider')
    enabled = property(_getenabled, _setenabled, lambda x: None,
                       'instance enabled status')
Esempio n. 22
0
class AdminAction(Entity):
    """Slick interface to invoke exposed methods via blaster protocol

       Requirement: droned.services.drone must be running to access methods
       that are exposed via this interface, this is configurable in DroneD's
       config.py settings.

       Developer Notes:
           how to use this interface ...

           1) instantiate with the name of the "action" you wish to expose
           2) expose methods see AdminAction.expose
           3) build documentation ... call AdminAction.buildDoc

       Examples:
           code:
               foo = AdminAction('bar')
               foo.expose('baz', lambda: 'hello world' (), 'example of AdminAction')
               foo.buildDoc()

           admin:
               #shell: << droneblaster bar ### or ### droneblaster help bar
               #shell: >> 127.0.0.1:5500  -> -4: "Usage: bar <command> [options]
               #shell: >> 
               #shell: >>     foo         example of AdminAction
               #shell: >> "
               #shell: >> Run Time: 0.019 seconds

               INVOCATION
               #shell: << droneblaster bar foo
               #shell: >> 127.0.0.1:5500  -> 0: "hello world"
               #shell: >> Run Time: 0.017 seconds
    """
    implements(IDroneModelAdminAction)
    serializable = False

    def __init__(self, action):
        self.action = action
        self.exposedMethodInfo = []
        self.exposedMethods = {}

    def log(self, message):
        """where to send logging information"""
        logWithContext(type=self.action, route='console')(str(message))

    def buildDoc(self):
        """You might need this, so it is provided. Rebuilds help <action>"""
        self.__doc__ = "Usage: %s <command> [options]\n\n" % (self.action, )
        for name, args, doc in self.exposedMethodInfo:
            argStr = ' '.join(['<' + arg + '>' for arg in args])
            self.__doc__ += "  %s %s\t%s\n" % (name, argStr, doc)

    #FIXME, document this better and clean it up
    def resultContext(self, template, instance=None, **context):
        """Creates a dict containg relevant contextual information about a 
           result.  You can override this method and tailor it to your liking.
           We typically use this to pass verbose structured data to a master
           DroneD controller (not provided with DroneD core) so that it may
           quickly make decisions based on the result of it's previous command
           and control activities.

           IF you set 'error' in the **context this will raise a server error
           at the remote end. This can be good or bad depending on your outlook
           on exceptions.  Consider this your only warning.

           return dict
        """
        if 'application' not in context:
            context['application'] = self.action
        failure = context.pop('error', False)
        if isinstance(failure, Failure):
            if 'description' not in context:
                context['description'] = '[%s] %s: %s' % \
                        (self.action, getException(failure), failure.getErrorMessage())
            if 'code' not in context:
                context['code'] = -2
            context['error'] = True
            context['stacktrace'] = failure.getTraceback()
            self.log('Result context during exception\n%(stacktrace)s' %
                     context)
            return context  #failed so bad we need to shortcut out
        else:
            context['error'] = bool(failure)
        if instance:  #this was made for AppManager's
            if hasattr(instance, 'version'):
                context['version'] = instance.version
            if hasattr(instance, 'label'):
                context['label'] = instance.label
            if hasattr(instance, 'running'):
                context['running'] = instance.running
        try:  #fail-safe in case someone is a bonehead
            context['description'] = template % context
        except:
            failure = Failure()
            context['description'] = '[%s] %s: %s' % \
                    (self.action, getException(failure), failure.getErrorMessage())
            context['stacktrace'] = failure.getTraceback()
            if 'code' not in context:
                context['code'] = -2
        #be nice to blaster api and the remote client
        context.update({'code': context.get('code', 0)})
        return context

    def invoke(self, name, args):
        """Invoke Exposed Methods
           @param name (str) - name of method to invoke
           @param args (tuple) - arguments to pass to invoked method

           @return (defer.Deferred)
        """
        if name not in self.exposedMethods:
            return defer.fail(
                DroneCommandFailed(
                    self.resultContext(
                        "[%(application)s] Unknown method '%(method)s'",
                        method=name,
                        error='unknown method')))
        try:
            #our own form of maybeDeferred
            d = self.exposedMethods[name](*args)
            if isinstance(d, defer.Deferred):
                action = Action(' '.join([str(i) for i in \
                        (self.action, name) + tuple(args)]), d)
                return action.deferred
            elif isinstance(d, DroneCommandFailed):
                return defer.fail(d)
            elif isinstance(d, dict):
                return defer.succeed(d)
            elif isinstance(d, type(None)):
                #this just feels dirty
                return defer.succeed(d)
            elif isinstance(d, Failure):
                d.raiseException()  #sigh
            #probably from a triggerred Event callback
            elif type(d) == types.InstanceType:
                return defer.succeed(None)
            return defer.fail(FormatError("Result is not formatted correctly you " + \
                 "must return self.resultContext or DroneCommandFailed." + \
                 "\nResult: <%s>" % (str(d),)))
        except:
            failure = Failure()
            if failure.check(DroneCommandFailed):
                template = "[%(application)s] %(description)s"
                context = failure.value.resultContext
                if not 'description' in context:
                    context['description'] = failure.getErrorMessage()
            else:
                template = "[%(application)s] " + "%s: %s" % (
                    getException(failure), failure.getErrorMessage())
                context = {
                    'error': True,
                    'code': -2,
                    'stacktrace': failure.getTraceback()
                }
            return defer.fail(
                DroneCommandFailed(
                    self.resultContext(template, None, **context)))

    @defer.deferredGenerator
    def __call__(self, argstr):
        args = argstr.split()
        resultContext = None
        if not args:  #return command usage
            methods = {}
            for name, args, doc in self.exposedMethodInfo:
                methods[name] = {'args': args, 'doc': doc}
            resultContext = dict(description=self.__doc__, methods=methods)
            yield resultContext
        else:
            method = args.pop(0)
            try:
                wfd = defer.waitForDeferred(self.invoke(method, args))
                yield wfd
                resultContext = wfd.getResult()
            except:
                failure = Failure()
                if failure.check(DroneCommandFailed):
                    resultContext = failure.value.resultContext
                else:
                    #be nice and return something to the end user
                    template = "[%(application)s] "
                    template += "%s: %s" % (getException(failure),
                                            failure.getErrorMessage())
                    context = {
                        'error': True,
                        'code': -2,
                        'stacktrace': failure.getTraceback()
                    }
                    resultContext = self.resultContext(template, None,
                                                       **context)

            yield resultContext

    def expose(self, name, method, args, doc):
        """Exposes a method in 'self.action name *args' to the class
           droned.services.drone.DroneServer for remote invocation over
           the blaster protocol

           @param name: (string)
           @param method: (callable)
           @param args: tuple((string), ...) -> named positional arguments
           @param doc: (string) -> administative documentation .. (ie usage)

           @return None
        """
        if name in self.exposedMethods:
            raise AttributeError('method %s is already reserved in %s' % \
                    (name, str(self)))
        self.exposedMethodInfo.append((name, args, doc))
        self.exposedMethods[name] = method

    def unexpose(self, name):
        """Removes an exposed method

           @param name: (string)

           @return None
        """
        #check the method dictionary first
        if name in self.exposedMethods:
            del self.exposedMethods[name]
        info = None  #locally scoped
        try:  #make sure the documentation is up to date
            for info in self.exposedMethodInfo:
                if info[0] != name: continue
                raise StopIteration('Found Method')
        except StopIteration:
            self.exposedMethodInfo.remove(info)

    @staticmethod
    def byName(action):
        for obj in AdminAction.objects:
            if obj.action == action:
                return obj
        return None
Esempio n. 23
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))
Esempio n. 24
0
class ApplicationPlugin(object):
    """Basis for DroneD Application Management.  There are some expectations
       that you must meet for this to work.  All started apps must daemonize
       ie fork away from DroneD.  The protocol must return the application pid
       or a failure if it does not the call will be cancelled and your app will
       terminate. take a look "__init__" to get an idea on the attributes that
       need to be setup, defaults are provided.

           read documentation of twisted.internet.reactor.spawnProcess and read
           droned.clients.__init__.command to see how it is being used.

           you need to define the following in your implementation of this class.
           
               name: (str) should be the name of your application

               #how to manage your application
               STARTUP_INFO: (dict)
               SHUTDOWN_INFO: (dict)

               #protocols to handle your application
               startProtocol: (class(droned.protocols.application.ApplicationProtocol))
               stopProtocol: (class(droned.protocols.application.ApplicationProtocol))

               #arguments to protocols
               startProtoArgs: (tuple)
               stopProtoArgs: (tuple)

               #keyword arguments to protocols
               startProtoKwargs: (dict)
               stopProtoKwargs: (dict)

           #further reading ...
           see droned.applications.ApplicationPlugin.__init__ and
           see droned.applications.ApplicationStorage.__init__ for
           the implementation specific details.

       This class and its derivatives use a metaclass to add storage
       capabilities.  also note that some of the instance methods are
       actually implemented inside of the metaclass.

       !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
       All plugins become singletons based on the ``class`` and ``name``.
       If you are extending another plugin class definition DONOT call it's
       __init__ method.  MRO and Singleton pattern will cause you hell.
       !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    """
    implements(IDroneDApplication)

    def __init__(self, *args, **kwargs):
        """The DroneD Service that loads ApplicationPlugins DOES NOT pass custom
           arguments or keyword parmeters to the constructor.  So you should
           not create constructors that expect external configuration. If you
           need external configuration you should implement a library to use for
           this purpose.  Default settings are provided in '__init__'.

           #all of these settings are required

           #provided by the metaclass as well
           #self.name = name
           #self.INITIALIZED = False

           #the metaclass provides these as defaults
           # self.STARTUP_INFO = {
           #    'START_USEPTY' : 0,
           #    'START_CHILDFD' : {0:'w',1:'r',2:'r'},
           #    'START_ENV' : {},
           #    'START_PATH' : None,
           #    'START_CMD' : '/bin/true',
           #    'START_ARGS' : (),
           # }
           # self.SHUTDOWN_INFO = { 
           #    'STOP_USEPTY' : 0,
           #    'STOP_CHILDFD' : {0:'w',1:'r',2:'r'},
           #    'STOP_ENV' : {},
           #    'STOP_PATH' : None,
           #    'STOP_CMD' : '/bin/true',
           #    'STOP_ARGS' : (),
           # }
           #
           #these protocol settings must be defined the metaclass will
           #provide sane defaults, that work for 90% of use cases
           #
           # self.startProtocol = ApplicationProtocol
           # self.startProtoArgs = () #protocol constructor *args
           # self.startProtoKwargs = {} #protocol constructor **kwargs
           # 
           # self.stopProtocol = ApplicationProtocol
           # self.stopProtoArgs = () #protocol constructor *args
           # self.stopProtoKwargs = {} #protocol constructor **kwargs
        """

    @defer.deferredGenerator
    def recoverInstance(self, occurance):
        """Recover Crashed Instances of the Application.
           this method should be subscribed to Event('instance-crashed')

           @param occurance: (object)
           @return defer.Deferred()
        """
        #check to make sure this is one of our instances
        if occurance.instance.app.name == self.name:
            self.log('application crashed restarting')
            #by default go through the AppManager
            d = self.service.startInstance(occurance.instance.label)
            d.addCallback(lambda x: self.log('sucessfully restarted') and x)
            d.addErrback(
                lambda x: self.log('failed to recover from crash') and x)
            result = None
            try:
                wfd = defer.waitForDeferred(d)
                yield wfd
                yield wfd.getResult()
            except:
                failure = Failure()
                self.log('throttling restart attempts')
                d = defer.Deferred()
                self.reactor.callLater(10, d.callback, None)
                wfd = defer.waitForDeffered(d)
                yield wfd
                wfd.getResult()
                yield failure
        else:
            yield 'not my application instance'

    @defer.deferredGenerator
    def startInstance(self, label):
        """Starts an Application Instances based on our models rules

           @param label: (string) - app instance label

           @return defer.Deferred - be prepared for Failures()
        """
        start = dictwrapper(copy.deepcopy(self.STARTUP_INFO))

        #configure our protocol
        if 'debug' not in self.startProtoKwargs:
            self.startProtoKwargs['debug'] = False
        if 'timeout' not in self.startProtoKwargs:
            self.startProtoKwargs['timeout'] = self.DEFAULT_TIMEOUT
        if 'logger' not in self.startProtoKwargs:
            self.startProtoKwargs['logger'] = self.log

        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        #
        # !! READ THIS COMMENT BLOCK !!
        #
        # the callback result from your protocol should return a dictionary
        # with a KEY 'pid' included and 'pid' should be an integer otherwise
        # the injected callback immediately following 'command' will fail to
        # update your instance state.  You have been warned.
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        result = None
        try:
            thisInst = self.getInstance(label)
            TIME = str(time.time())
            anchor = DIGEST_INIT()
            anchor.update(self.name)
            anchor.update(label)
            anchor.update(TIME)  #add some randomness
            anchor = str(anchor.hexdigest())
            #inject some env variables that we may want to retrieve later
            ENV = {
                'DRONED_IDENTIFIER': anchor,
                'DRONED_STARTTIME': TIME,
                'DRONED_LABEL': label,
                'DRONED_APPLICATION': self.name,
                'DRONED_LOGDIR': config.LOG_DIR,
            }
            if thisInst.version:
                ENV['DRONED_VERSION'] = thisInst.version
            #add these vars to the start env of the contained application
            start.START_ENV.update(ENV)
            d = command(start.START_CMD, start.START_ARGS, start.START_ENV,
                        start.START_PATH, start.START_USEPTY,
                        start.START_CHILDFD, self.startProtocol,
                        *self.startProtoArgs, **self.startProtoKwargs)
            wfd = defer.waitForDeferred(d)
            yield wfd
            result = wfd.getResult()  #we probably might not know the pid yet
            #if allowed by config search for the instance after some delay
            pid = result.get('pid', 0)  #just in case the protocol knows it
            if isinstance(self.SEARCH_DELAY, (int, float)) and not pid:
                d = defer.Deferred()
                self.reactor.callLater(self.SEARCH_DELAY, d.callback, None)
                wfd = defer.waitForDeferred(d)
                yield wfd
                wfd.getResult()  #don't care about this result
                d = self.findProcesses()
                wfd = defer.waitForDeferred(d)
                yield wfd
                data = wfd.getResult()
                if data:
                    data = data.pop(0)[1]  #we are just going to take the first
                    result.update(data)  #we should have the pid captured
            #this is the proper way to notify the AppInstance that we are running
            thisInst.pid = int(result.get('pid', 0))
        except:
            result = Failure()
        yield result

#NOTE runs hot due to IO read on Linux and Solaris

    @deferredAsThread
    def findProcesses(self):
        """Attempt to find a process by an ASSIMILATION pattern.
           This is a relatively naive attempt to find an application
           that works in most cases. 

           NOTE:
             If your ASSIMILATION pattern includes group matches the 
             dictionary will be updated with the output of
             ``groupdict() from re.search``.  If droned is able to
             read the environment settings from the application that
             will be inlcuded in the result dictionary as well. 


           @callback (list) -  sorted([(int('PID'), dict), ...])
           @errback (twisted.python.failure.Failure())
           @return defer.Deferred()
        """
        candidates = {}

        def safe_process(pid):
            try:  #because processes can be invalid
                return AppProcess(Server(config.HOSTNAME), pid)
            except:
                err('problem')
                return None

        if self.PROCESS_REGEX:
            #rescan the whole system for processes
            for process in (safe_process(pid) for pid in listProcesses()):
                try:
                    if not process: continue
                    if not process.__class__.isValid(process): continue
                    if process.pid in candidates: continue
                    if process.ppid != 1: continue  #droned wants your daemons
                    if not process.running: continue
                    if not process.localInstall: continue
                    if process.managed: continue  #already managed
                    cmd = ' '.join(process.cmdline)
                    if not cmd: continue
                    match = self.PROCESS_REGEX.search(cmd)
                    if not match: continue
                    #remember we tried to set some VARS on startInstance
                    _result = dict(**process.environ)
                    #allows us to set interesting parameters in the regex
                    _result.update(match.groupdict())
                    _result.update({'pid': process.pid})
                    candidates[_result['pid']] = _result
                except:
                    self.log('error searching for process check console log')
                    err('error searching for process')
        return sorted([(pid, d) for (pid, d) in candidates.items() if pid > 1])

    @defer.deferredGenerator
    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 stopInstance(self, label):
        """Stops an Application Instances based on our models rules

           @param label: (string)      - app instance label

           @return defer.Deferred - be prepared for Failures()
        """
        stop = dictwrapper(self.SHUTDOWN_INFO)

        #configure our protocol
        if 'debug' not in self.stopProtoKwargs:
            self.stopProtoKwargs['debug'] = False
        if 'timeout' not in self.stopProtoKwargs:
            self.stopProtoKwargs['timeout'] = self.DEFAULT_TIMEOUT
        if 'logger' not in self.stopProtoKwargs:
            self.stopProtoKwargs['logger'] = self.log

        return command(stop.STOP_CMD, stop.STOP_ARGS, stop.STOP_ENV,
                       stop.STOP_PATH, stop.STOP_USEPTY, stop.STOP_CHILDFD,
                       self.stopProtocol, *self.stopProtoArgs,
                       **self.stopProtoKwargs)

    def setVersion(self, label, version):
        """sets the version of this instance

           I attempt to delegate to the original provider.

           @param label: (string)
           @param version: (string)

           @return AppInstance()
        """
        version = AppVersion.makeVersion(self.name, version)
        thisInst = None

        thisInst = self.getInstance(label)

        if thisInst.version != version:
            thisInst.version = version
        return thisInst

    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 delInstance(self, label):
        """delete an application instance

           I attempt to delegate to the original provider.

           @param label (string)

           @return None
        """
        label = str(label)
        thisInst = self.getInstance(label)
        if thisInst.running:
            raise AssertionError('cannot delete running application instance')
        AppInstance.delete(thisInst)

    def statusInstance(self, label):
        """Report the status of our Application Instance"""
        myInst = self.getInstance(label)
        result = {
            'name': self.name,
            'label': myInst.label,
            'enabled': bool(myInst.enabled),
            'running': myInst.running,
            'version': myInst.version,
            'pid': myInst.pid,
            'ppid': myInst.ppid,
            'inode': myInst.inode,
            'crashed': bool(myInst.crashed),
            'threads': myInst.threads,
            'memory': int(myInst.memory),
            'files': myInst.fd_count,
            'cpu': float(myInst.cpu),
        }
        state = str(getattr(myInst, 'state', 'unknown'))
        description = '%s is %s.' % (myInst.description, state)
        result['state'] = state
        result['description'] = description
        return result

    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 expose(self, *args, **kwargs):
        """expose methods via the L{IDroneModelAppManager} provider"""
        return self.service.expose(*args, **kwargs)

    def unexpose(self, *args, **kwargs):
        """unexpose methods via the L{IDroneModelAppManager} provider"""
        return self.service.unexpose(*args, **kwargs)

    def log(self, *args, **kwargs):
        """log messages via the L{IDroneModelAppManager} provider"""
        return self.service.log(*args, **kwargs)
Esempio n. 25
0
class Process(_SunProc):
    """base class for processes"""
    implements(IKittProcess)

    running = property(lambda s: s.isRunning())
    memory = property(lambda s: s.memUsage())
    stats = property(lambda s: s.getStats())
    environ = property(lambda s: s.getEnv())
    threads = property(lambda s: len(s.getTasks()))
    fd_count = property(lambda s: len(s.getFD()) or 3)

    def __init__(self, pid):
        self.pid = int(pid)
        self.path = "%s/%d" % (PROCDIR, int(self.pid))
        if not os.path.isdir(self.path):
            #this exception is injected into the module on import
            raise AssertionError("Invalid PID (%s)" % pid)
        self.inode = os.stat(self.path).st_ino
        _SunProc.__init__(self)

    def isRunning(self):
        """is a process running
           @return (bool)
        """
        try:
            if self.pid > 0:
                #in case the process is an unreaped child
                os.waitpid(self.pid, os.WNOHANG)
        except:
            pass
        try:
            return os.stat(self.path).st_ino == self.inode
        except:
            return False

    def waitForDeath(self, timeout=10, delay=0.25):
        """wait for process to die

           @return (bool)
        """
        while timeout > 0:
            if not self.isRunning(): return True
            time.sleep(delay)
            timeout -= delay
        return False


#FIXME

    def getEnv(self):
        """the environment settings from the processes perpective,
           @return (dict)
        """
        env = {}
        return env

    def getFD(self):
        """Get all open file descriptors
           @return (dict)
        """
        fd = {}
        try:  #can't decide if we should 'path' or 'fd'
            for link in os.listdir('%s/fd' % self.path):
                try:
                    fd[link] = os.readlink('%s/fd/%s' % (self.path, link))
                except:
                    pass
        except:
            pass
        return fd

    def getTasks(self):
        """Get all open tasks/threads
           @return (set)
        """
        try:
            taskDir = os.path.join(self.path, 'lwp')
            if os.path.exists(taskDir):
                return set(map(int, os.listdir(taskDir)))
            else:
                return set()
        except:
            return set()

    def getStats(self):
        """Get the process' stats
           @return (dict)
        """
        #get first dictionary
        x = self.readFile('psinfo')
        #get second dictionary
        y = self.readFile('status')
        #update first with contents of second
        return dict(x, **y)

    def memUsage(self):
        """Get the process' stats
           @return (dict)
        """
        if not hasattr(self, 'size'):
            size = self.readFile('psinfo')['size']
            self.size = size
        return (self.size * 1024)  #b/c it is in Kb

    def __str__(self):
        return '%s(pid=%d)' % (self.__class__.__name__, self.pid)

    __repr__ = __str__
Esempio n. 26
0
class Process(object):
    """base class for processes"""
    implements(IKittProcess)

    running = property(lambda s: s.isRunning())
    memory = property(lambda s: s.memUsage())
    stats = property(lambda s: s.getStats())
    environ = property(lambda s: s.getEnv())
    threads = property(lambda s: len(s.getTasks()))
    fd_count = property(lambda s: len(s.getFD()) or 3)

    def __init__(self, pid):
        self.pid = int(pid)
        self.path = "%s/%d" % (PROCDIR, pid)
        if not os.path.isdir(self.path):
            self.path = "%s/.%d" % (PROCDIR, pid)  #For kernel 2.4 threads
            if not os.path.isdir(self.path):
                #this exception is injected into the module on import
                raise AssertionError("Invalid PID (%s)" % pid)
        self.inode = os.stat(self.path).st_ino

    def readFile(self, f):
        return open('%s/%s' % (self.path, f)).read()

    def isRunning(self):
        """is a process running
           @return (bool)
        """
        try:
            if self.pid > 0:
                #in case the process is an unreaped child
                os.waitpid(self.pid, os.WNOHANG)
        except:
            pass
        try:
            return os.stat(self.path).st_ino == self.inode
        except:
            return False

    def waitForDeath(self, timeout=10, delay=0.25):
        """wait for process to die

           @return (bool)
        """
        while timeout > 0:
            if not self.isRunning(): return True
            time.sleep(delay)
            timeout -= delay
        return False

    def getEnv(self):
        """the environment settings from the processes perpective,
           @return (dict)
        """
        env = {}
        try:
            envlist = self.readFile('environ').split('\000')
        except:
            return env
        for e in envlist:
            if '=' in e:
                k, v = e.split('=', 1)
            else:
                k, v = e, ''
            k, v = k.strip(), v.strip()
            if not k: continue
            env[k] = v
        return env

    def getFD(self):
        """Get all open file descriptors
           @return (dict)
        """
        fd = {}
        try:
            for link in os.listdir('%s/fd' % self.path):
                try:
                    fd[link] = os.readlink('%s/fd/%s' % (self.path, link))
                except:
                    pass
        except:
            pass
        return fd

    def getTasks(self):
        """Get all open tasks/threads
           @return (set)
        """
        try:
            if KERNEL26 or KERNEL3x:
                taskDir = os.path.join(self.path, 'task')
                if os.path.exists(taskDir):
                    return set(map(int, os.listdir(taskDir)))
                else:
                    return set()
            else:
                my_pid = self.pid  #Attribute lookup is heavy for LiveProcess's
                my_threads = set()
                for entry in os.listdir(PROCDIR):
                    if entry[0] != '.': continue
                    try:
                        pid = int(entry[1:])
                        thread = LiveProcess(pid, fast=True)
                        if thread.tgid == my_pid:
                            my_threads.add(pid)
                    except:
                        pass
                return my_threads
        except:
            pass
        return set()

    def getStats(self):
        """Get the process' stats
           @return (dict)
        """
        stats = {}
        statstr = self.readFile('stat')
        begin, end = statstr.find('('), statstr.rfind(')')
        comm = statstr[begin + 1:end]
        statlist = [comm] + statstr[end + 2:].split(' ')
        for i in range(len(stat_attribs)):
            try:
                stats[stat_attribs[i]] = int(statlist[i])
            except:
                try:
                    stats[stat_attribs[i]] = statlist[i]
                except IndexError:
                    pass  #2.4 kernels don't have last 2 stat_attribs
        return stats

    def memUsage(self):
        """Returns resident memory used in bytes
           @return (int)
        """
        return int(self.rss * PAGESIZE)

    def __str__(self):
        return '%s(pid=%d)' % (self.__class__.__name__, self.pid)

    __repr__ = __str__
Esempio n. 27
0
class App(Entity):
    """track applications"""
    implements(IDroneModelApp)
    managedOn = property( lambda self: set(server for server in \
            Server.objects if not server.unreachable and self in \
            server.droned.apps) )
    configuredOn = property( lambda self: set(i.server for i in \
            self.appinstances) )
    appversions  = property( lambda self: (av for av in \
            AppVersion.objects if av.app is self) )
    appinstances = property( lambda self: (ai for ai in \
            AppInstance.objects if ai.app is self) )
    localappinstances = property( lambda self: (i for i in self.appinstances \
            if i.server.hostname == config.HOSTNAME) )
    runningInstances = property( lambda self: (i for i in self.appinstances \
            if i.running) )
    localrunningInstances = property( lambda self: (i for i in \
            self.runningInstances if i.server.hostname == config.HOSTNAME) )
    #rxContext = RxAppContextDescriptor()
    serializable = True

    @property
    def latestVersion(self):
        latest = None
        for av in self.appversions:
            if not latest:
                latest = av
            if av > latest:
                latest = av
        return latest

    def __init__(self, name):
        self.name = name
        self.shouldRunOn = set()

    def __getstate__(self):
        return {
            'name': self.name,
            'shouldRunOn': [server.hostname for server in self.shouldRunOn]
        }

    @staticmethod
    def construct(state):
        app = App(state['name'])
        app.shouldRunOn = set( Server(hostname) for hostname in \
                state['shouldRunOn'] )
        return app

    def runsOn(self, server):
        if server not in self.shouldRunOn:
            self.shouldRunOn.add(server)
            Event('app-servers-change').fire(app=self,
                                             server=server,
                                             change='added')

    def doesNotRunOn(self, server):
        if server in self.shouldRunOn:
            self.shouldRunOn.remove(server)
            Event('app-servers-change').fire(app=self,
                                             server=server,
                                             change='removed')
Esempio n. 28
0
class AppProcess(Entity):
    """Track Processes that are associated with an AppInstance"""
    implements(IDroneModelAppProcess)
    created = property(lambda s: s._created)
    managed = property(lambda s: AppProcess.isValid(s) and \
            isinstance(s.appinstance, AppInstance) and \
            s.appinstance.__class__.isValid(s.appinstance))
    #    serializable = True
    localInstall = property(lambda s: bool(s.server.hostname == \
            config.HOSTNAME))

    pid = property(lambda s: s._pid)
    valid = property(lambda s: AppProcess.isValid(s) and s.running)

    def __getattribute__(self, name):
        """Overrode to fulfill our interface obligations"""
        if name in ('running','ppid','memory','fd_count','stats','threads',\
                'exe','environ','cmdline','inode'):
            try:
                return self.process.__getattribute__(name)
            except:
                delattr(self, '_process')
                #try one more time, just because we should
                return self.process.__getattribute__(name)
                #treat any failure like we are no longer running
        return object.__getattribute__(self, name)

    def __init__(self, server, pid):
        self._pid = pid
        self.server = IDroneModelServer(server)
        self._created = time.time()
        #don't set self._process
        try:
            try:  #re-constructing, can cause problems with this
                if IKittNullProcess.providedBy(self.process.process):
                    raise InvalidProcess("Invalid PID (%s)" % pid)
            except AttributeError:
                if isinstance(self.process, NullProcess):
                    raise InvalidProcess("Invalid PID (%s)" % pid)
                raise  #re-raise do avoid ending up in a pickle, literally
        except InvalidProcess:
            if config.HOSTNAME == self.server.hostname:
                AppProcess.delete(self)  #make sure we are invalid
                raise InvalidProcess("Invalid PID (%s)" % pid)
        except IOError:  #linux and solaris kitt.proc.LiveProcess use files
            AppProcess.delete(self)  #make sure we are invalid
            raise InvalidProcess("Invalid PID (%s)" % pid)
        except:
            err('wtf happened here .. seriously i do not know!!!')
            AppProcess.delete(self)  #make sure we are invalid
            raise

    @property
    def process(self):
        """@return L{IKittProcess} provider"""
        #work around constructor and allow a process to appear to die
        if not hasattr(self, '_process'):
            #adapt this model to a IKittProcess
            self._process = IKittProcess(self)
        elif self._process.pid != self.pid or not self._process.running:
            if config.HOSTNAME == self.server.hostname:
                AppProcess.delete(
                    self)  #take ourself out of serialization loop
                self._process = IKittNullProcess(
                    self)  #continue to work for any other refs
        return self._process

    @staticmethod
    def construct(state):
        server = Server(state.pop('server'))
        ap = None
        try:
            ap = AppProcess(server, state['pid'])
        except:
            return None
        if ap.localInstall and ap.inode != state['inode']:
            AppProcess.delete(ap)
            return None
        ap._created = state.pop('created')
        #we know this is going throw an adapter
        if IKittRemoteProcess.providedBy(ap.process.process):
            #remaining attributes go into the remote process
            ap.updateProcess(state)  #this is only useful to remote processes
        return ap

    def __getstate__(self):
        #save enough state so that remote processes are useful
        #account for scabs and appinstances
        data = {
            'created': self.created,
            'managed': self.managed,
            'server': self.server.hostname,  #even scabs have this attr  
            'pid': self.pid,
            'inode': self.inode,  #used to determine if the pid is valid
            'running': self.running,
            'memory': self.memory,
            'ppid': self.ppid,
            'fd_count': self.fd_count,
            'stats': self.stats,
            'threads': self.threads,
            'exe': self.exe,
            'cmdline': self.cmdline
        }
        #return managed process
        return data

    @property
    def children(self):
        """emits a generator of our child process objects

           this is only really useful for scab detection
           this only works if the child pid has been contained
           in another AppProcess Instance. Of course it could
           also be useful in bizarre situations with apps that
           have a parent supervisor and child worker.
        """
        for process in AppProcess.objects:
            if IKittNullProcess.providedBy(process.process): continue
            if not self.pid: break
            if process == self: continue
            if process.server.hostname != self.server.hostname: continue
            if process.ppid == self.pid and AppProcess.isValid(process):
                yield process

    @property
    def appinstance(self):
        """matches this AppProcess to an AppInstance

           @return L{IDroneModelAppInstance} provider or None
        """
        try:
            return IDroneModelAppInstance(self)
        except:
            return None
Esempio n. 29
0
class LiveProcess(object):
    """provide a droned compatible interface by delegating
       the heavy work to ``psutil`` if it is available.

       see kitt.interfaces.process.IKittProcess for a
       full api description.
    """
    implements(IKittLiveProcess)
    ps = property(lambda s: s._delegate)
    running = property(lambda s: s.isRunning())
    memory = property(lambda s: s.ps.get_memory_info().rss)
    inode = property(lambda s: s._saved_inode)
    fd_count = property(lambda s: len(s.getFD()))
    stats = property(lambda s: s.getStats())
    environ = property(lambda s: s.getEnv())

    cmdline = property(lambda s: s._cmdline)
    exe = property(lambda s: s._exe)
    uid = property(lambda s: s._uid)
    gid = property(lambda s: s._gid)
    pid = property(lambda s: s._pid)
    ppid = property(lambda s: s.ps.ppid)  #could be reparented

    @property
    def threads(self):
        try:
            return self.ps.get_num_threads()
        except:
            return 1

    def _make_exe(self):
        """work around access errors"""
        try:
            return self.ps.exe
        except:  #work around not having any args to guess at
            try:
                return self.cmdline[0]  #next best guess
            except IndexError:
                return self.ps.name  #totally making this up now

    def _make_inode(self):
        """may not be supported on all systems so we do it ourselves"""
        #on linux and solaris, we could just check the inode of the
        #process as os.stat(/proc/%(PID)d).st_ino, but this is obviously
        #not portable, so we will just hash some static values from the
        #process and hope for the best. DroneD needs the inode to track
        #long running applications easily even if droned is down for
        #long periods of time.
        if HAS_PROC_DIR:  #this is an optimization
            return os.stat(os.path.join(PROC_DIR, str(self.pid))).st_ino
        #this may use more IO than we like
        return hash((self.exe, self.pid, self.ps.create_time, self._name) + \
                tuple(self.ps.cmdline)) & 0xffffffff #make sure return is positive

    def __init__(self, pid):
        if type(pid) != int:
            raise ValueError('Pid must be an integer')
        self._pid = pid
        self._delegate = psutil.Process(pid)
        #avoid looking these attr's up all of the time.
        #IO is expensive and the following attr's should be
        #static anyhow.
        self._name = self.ps.name
        self._cmdline = self.ps.cmdline
        self._uid = self.ps.uids.real
        self._gid = self.ps.gids.real
        self._exe = self._make_exe()
        #save this for running tests
        self._saved_inode = self._make_inode()
        if not self.running:
            raise AssertionError("Invalid PID (%d)" % self.pid)

    def isRunning(self):
        """make sure not only the pid is running but it is the same process 
           we thought it was.  this may be done naively with a simple hash.
        """
        if HAS_PROC_DIR:  #this is an optimization
            if not os.path.exists(os.path.join(PROC_DIR, str(self.pid))):
                return False
        elif not self.ps.is_running():
            return False
        #make sure this is the same process we thought it was
        return bool(self._make_inode() == self.inode)

    def getEnv(self):
        """not portable so not implemented"""
        return {}

    def getFD(self):
        #every os has stdin, stdout, and stderr
        FDS = {0: None, 1: None, 2: None}
        try:
            FDS.update(dict((i.fd, i.path) for i in self.ps.get_open_files()))
        except:
            pass
        return FDS

    def getTasks(self):
        """get the thread id's"""
        try:
            return set(t.id for t in self.ps.get_threads())
        except:
            return set()

    def getStats(self):
        """not portable so not implemented"""
        return {}

    def memUsage(self):
        """get memory usage in bytes"""
        return self.memory

    def waitForDeath(self, timeout=10, delay=0.25):
        """wait for the process to die"""
        #delay isn't needed, but it is part of the interface
        #definition, so we'll leave it as a dummy.
        try:
            self.ps.wait(timeout)
        except:
            pass  #not sure if it is needed
        return not self.running

    def cpuUsage(self):
        cpu = self.ps.get_cpu_times()
        return {'user_util': cpu.user, 'sys_util': cpu.system}

    def __str__(self):
        return '%s(pid=%d)' % (self.__class__.__name__, self.pid)

    __repr__ = __str__
Esempio n. 30
0
class ApplicationEvent(Entity):
    """The Extents Eventing For AppManagers"""
    implements(IDroneModelApplicationEvent)
    serializable = False
    reactor = property(lambda s: config.reactor)

    def __del__(self):
        if self.loop.running: self.loop.stop()
        try:
            Event.delete(self.event)
        except:
            pass

#TODO document

    def __init__(self, service, name, callback, **kwargs):
        """
           
        """
        #get ready for python3
        if not hasattr(callback, '__call__'):
            raise AssertionError("%s is not callable" % (callback.__name__, ))

        event_name = str(service) + '-' + str(name)
        self.event = Event(event_name)
        self.name = name
        self.loop = None
        self.service = service

        #borderline laziness on my part
        self.log = AppManager(service).log

        self.condition = kwargs.get('condition', None)
        self.recurring = float(kwargs.get('recurring', 0))
        self.silent = kwargs.get('silent', False)

        assert not (self.recurring and self.condition), \
            "recurring and condition args are mutually exclusive"

        self.func = callback.__name__
        if not self.silent:
            self.event.subscribe(self.announce)
        #send data only if we have it, keeps compatibility with legacy api
        self.event.subscribe(lambda x: (hasattr(x, 'params') and x.params) \
            and callback(x) or callback())

        #our event loop is controlled by the Service
        if self.recurring > 0.0:
            self.loop = LoopingCall(self.event.fire)
            self.occurred()  #automatically start re-occuring events

    def announce(self, *args, **kargs):
        """Anounce the Occurrence of an Event"""
        self.log("%s event occurred calling %s" % (self.name, self.func))
        return True

    def occurred(self):
        """Check for Occurrence or Start Event Loop"""
        if not self.condition and self.loop and not self.loop.running:
            self.loop.start(self.recurring)
        elif self.condition and self.condition():
            self.event.fire()
            return True
        return False

#TODO document

    def trigger(self, data=None, delay=0.0):
        """Trigger an event, the return object can be cancelled"""
        kwargs = {}
        if data:
            kwargs['data'] = data
        return self.reactor.callLater(delay, self.event.fire, **kwargs)