fixbot/api.py
author Tero Marttila <terom@fixme.fi>
Thu, 04 Feb 2010 18:31:34 +0200
changeset 35 5b6043ce9686
parent 30 33527d91b6f6
child 39 e82b6df5baa3
permissions -rw-r--r--
kill off the fixbot/*.py.dist config files, prepare for reading them from etc/, add an --api-secret option. Breaks logwatch
from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.python import log, usage
from datetime import datetime
import sys

import buffer

class ModuleInfo (object) :
    """
        Some info about a module
    """

    # module's name
    name = None

    # module's version, as a 16-bit integer
    version = None

    # list of valid event types (strings)
    event_types = None

    def __str__ (self) :
        return "Module %s:%d" % (self.name, self.version)
    
    def __repr__ (self) :
        return "<module %s:%d with events: %s>" % (self.name, self.version, ", ".join(self.event_types))

class Event (object) :
    # the ModuleInfo object
    module = None

    # the event type as a string
    type = None

    # event message as a string (under 255 bytes in length!)
    msg = None

    # timestamp as a datetime.datetime
    when = None

    def __init__ (self, module, type, msg) :
        assert type in module.event_types, "Invalid event-type %s for %r" % (type, self.module)
        
        self.module = module
        self.type = type
        self.msg = msg

        self.when = datetime.now()
    
    def __str__ (self) :
        return "[%s] %s" % (self.type, self.msg)
    
    def __repr__ (self) :
        return "%s @ %s" % (self.type, self.when)
 
CLIENT_COMMANDS = [
    "module_init",
    "module_event",
]

SERVER_COMMANDS = [
    "module_ok",
]
       
class ServerProtocol (buffer.StreamProtocol, protocol.Protocol) :
    RECV_COMMANDS = CLIENT_COMMANDS
    SEND_COMMANDS = SERVER_COMMANDS    

    VALID_STATES = [
        "wait_init",
        "wait_event"
    ]
    
    # proto state
    state = None

    # module info
    module = None
    
    def _assert (self, condition, msg) :
        if not condition :
            self.transport.loseConnection()
            log.err("assert failed in APIProtocol for %s: %s" % (self.module, msg))
    
    def connectionMade (self) :
        log.msg("Client connected")

    def connectionLost (self, reason) :
        log.msg("Connection lost: %s" % reason)
        
        if self.module :
            self.factory.nexus.unregisterModule(self.module, reason.getErrorMessage())

    def on_module_init (self, i) :
        self._assert(not self.module, "module_init with non-None self.module")

        peer_secret = i.readVarLen('B')
        
        self._assert(peer_secret == self.factory.secret, "Mismatching API secrets!")

        m = ModuleInfo()
        
        m.name = i.readVarLen('B')
        m.version = i.readItem('H')

        m.event_types = list(buffer.readStringStream(i, 'B'))
        m.addr = self.transport.getPeer()

        self.module_name = m.name

        log.msg("Got mod_init for %r" % m)
        
        self.factory.nexus.registerModule(m, self)

        self.module = m

        o = self.startCommand('module_ok')

        self.send(o)

    def on_module_event (self, i) :
        self._assert(self.module, "module_event with None self.module!")

        event_type = i.readEnum(self.module.event_types)
        event_msg = i.readVarLen('H')
        
        e = Event(self.module, event_type, event_msg)

#        log.msg("Got mod_event of %r" % (e))

        self.factory.nexus.handleEvent(e)

    def logPrefix (self) :
        if self.module :
            return str(self.module)
        else :
            return super(ServerProtocol, self).logPrefix()

class ClientProtocol (buffer.StreamProtocol, protocol.Protocol) :
    RECV_COMMANDS = SERVER_COMMANDS
    SEND_COMMANDS = CLIENT_COMMANDS

    def connectionMade (self) :
        log.msg("Connected to API server, sending module init message")

        o = self.startCommand('module_init')
        o.writeVarLen('B', self.factory.secret)
        o.writeVarLen('B', self.factory.name)
        o.writeItem("H", self.factory.version)
        buffer.writeStringStream(o, 'B', self.factory.event_types)

        self.send(o)

    def sendEvent (self, event) :
        o = self.startCommand('module_event')
        o.writeEnum(self.factory.event_types, event.type)
        o.writeVarLen('H', event.msg[:2**16])

        self.send(o)

    def on_module_ok (self, i) :
        log.msg("Registration OK")

        self.factory.connected(self)
    
    def logPrefix (self) :
        return "module %s:%d client" % (self.factory.name, self.factory.version)

class Module (ModuleInfo, protocol.ClientFactory) :
    protocol = ClientProtocol

    def __init__ (self, secret) :
        self.connection = None
        self.secret = secret

    def connected (self, connection) :
        log.msg("Connected!")
        self.connection = connection

        self.handleConnect()

    def disconnect (self) :
        self.connection.transport.loseConnection()
    
    def sendEvent (self, type, msg) :
        self.connection.sendEvent(self.buildEvent(type, msg))

    def buildEvent (self, type, msg) :
        return Event(self, type, msg)

    def handleConnect (self) :
        """
            Do something
        """

        pass

class ServerFactory (protocol.ServerFactory) :
    protocol = ServerProtocol

    def __init__ (self, nexus, secret) :
        self.nexus = nexus
        self.secret = secret

class ClientOptions (usage.Options) :
    optParameters = [
        (   "api-server",   "s",    "127.0.0.1",        "address of API server to connect to"           ),
        (   "api-port",     "P",    34888,              "port of API server to connect to",     int     ),
        (   "api-secret",   None,   None,               "secret key for API connections"                ),
    ]

    optFlags = [

    ]

def makeService (client_module_factory, config) :
    s = service.MultiService()
    
    # the API client
    module_factory = client_module_factory(config['api-secret'])
    
    log.msg("Connecting to API server on [%s:%d]" % (config['api-server'], config['api-port']))
    api_client = internet.TCPClient(config['api-server'], config['api-port'], module_factory)
    
    api_client.setServiceParent(s)

    return s