]> git.ipfire.org Git - thirdparty/openembedded/openembedded-core-contrib.git/commitdiff
bitbake: xmlrpc remote server
authorAlexandru DAMIAN <alexandru.damian@intel.com>
Tue, 28 May 2013 16:52:02 +0000 (16:52 +0000)
committerRichard Purdie <richard.purdie@linuxfoundation.org>
Thu, 30 May 2013 09:30:25 +0000 (10:30 +0100)
Added code in XMLRPC server that creates a stub local server
for a client-only connection and is able to connect to
a remote server, and receive events from the remote server.

Added the option to start a client with a remote server in
bitbake.

Original code by Bogdan Marinescu <bogdan.a.marinescu@intel.com>

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
bin/bitbake
lib/bb/server/xmlrpc.py

index 6d4efe6bbfee2957b1d1561cfae3b123a41ee511..f3bbeb4667b34e1e286c47bf8cf57b624fc75cdf 100755 (executable)
@@ -190,8 +190,13 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
 
         parser.add_option("-B", "--bind", help = "The name/address for the bitbake server to bind to",
                    action = "store", dest = "bind", default = False)
+
         parser.add_option("", "--no-setscene", help = "Do not run any setscene tasks, forces builds",
                    action = "store_true", dest = "nosetscene", default = False)
+
+        parser.add_option("", "--remote-server", help = "Connect to the specified server",
+                   action = "store", dest = "remote_server", default = False)
+
         options, targets = parser.parse_args(sys.argv)
         return options, targets[1:]
 
@@ -260,6 +265,9 @@ def main():
     if configParams.bind and configParams.servertype != "xmlrpc":
         sys.exit("FATAL: If '-B' or '--bind' is defined, we must set the servertype as 'xmlrpc'.\n")
 
+    if configParams.remote_server and configParams.servertype != "xmlrpc":
+        sys.exit("FATAL: If '--remote-server' is defined, we must set the servertype as 'xmlrpc'.\n")
+
     if "BBDEBUG" in os.environ:
         level = int(os.environ["BBDEBUG"])
         if level > configuration.debug:
@@ -281,8 +289,13 @@ def main():
     else:
         configuration.extra_caches = getattr(ui_module, "extraCaches", [])
 
-    # we start a server with a given configuration
-    server = start_server(servermodule, configParams, configuration)
+    if not configParams.remote_server:
+        # we start a server with a given configuration
+        server = start_server(servermodule, configParams, configuration)
+    else:
+        # we start a stub server that is actually a XMLRPClient to
+        server = servermodule.BitBakeXMLRPCClient()
+        server.saveConnectionDetails(configParams.remote_server)
 
     logger.removeHandler(handler)
 
index e9c106b20e653de7789d99a5ea6efae7623e3a50..56a643c5764455ed465a6b3f96890da2441f435b 100644 (file)
@@ -35,6 +35,14 @@ import bb
 import xmlrpclib, sys
 from bb import daemonize
 from bb.ui import uievent
+import hashlib, time
+import socket
+import os, signal
+import threading
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
 
 DEBUG = False
 
@@ -175,11 +183,145 @@ class BitBakeServerCommands():
         print("Server (cooker) exiting")
         return
 
-    def ping(self):
+    def addClient(self):
+        if self.has_client:
+            return None
+        token = hashlib.md5(str(time.time())).hexdigest()
+        self.server.set_connection_token(token)
+        self.has_client = True
+        return token
+
+    def removeClient(self):
+        if self.has_client:
+            self.server.set_connection_token(None)
+            self.has_client = False
+
+# This request handler checks if the request has a "Bitbake-token" header
+# field (this comes from the client side) and compares it with its internal
+# "Bitbake-token" field (this comes from the server). If the two are not
+# equal, it is assumed that a client is trying to connect to the server
+# while another client is connected to the server. In this case, a 503 error
+# ("service unavailable") is returned to the client.
+class BitBakeXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
+    def __init__(self, request, client_address, server):
+        self.connection_token = server.connection_token
+        SimpleXMLRPCRequestHandler.__init__(self, request, client_address, server)
+
+    def do_POST(self):
+        try:
+            remote_token = self.headers["Bitbake-token"]
+        except:
+            remote_token = None
+        if remote_token != self.connection_token:
+            self.report_503()
+        else:
+            SimpleXMLRPCRequestHandler.do_POST(self)
+
+    def report_503(self):
+        self.send_response(503)
+        response = 'No more client allowed'
+        self.send_header("Content-type", "text/plain")
+        self.send_header("Content-length", str(len(response)))
+        self.end_headers()
+        self.wfile.write(response)
+
+
+class BitBakeUIEventServer(threading.Thread):
+    class EventAdapter():
         """
-        Dummy method which can be used to check the server is still alive
+        Adapter to wrap our event queue since the caller (bb.event) expects to
+        call a send() method, but our actual queue only has put()
         """
-        return True
+        def __init__(self, notify):
+            self.queue = []
+            self.notify = notify
+            self.qlock = threading.Lock()
+
+        def send(self, event):
+            self.qlock.acquire()
+            self.queue.append(event)
+            self.qlock.release()
+            self.notify.set()
+
+        def get(self):
+            self.qlock.acquire()
+            if len(self.queue) == 0:
+                self.qlock.release()
+                return None
+            e = self.queue.pop(0)
+            if len(self.queue) == 0:
+                self.notify.clear()
+            self.qlock.release()
+            return e
+
+    def __init__(self, connection):
+        self.connection = connection
+        self.notify = threading.Event()
+        self.event = BitBakeUIEventServer.EventAdapter(self.notify)
+        self.quit = False
+        threading.Thread.__init__(self)
+
+    def terminateServer(self):
+        self.quit = True
+
+    def run(self):
+        while not self.quit:
+            self.notify.wait(0.1)
+            evt = self.event.get()
+            if evt:
+                self.connection.event.sendpickle(pickle.dumps(evt))
+
+class BitBakeXMLRPCEventServerController(SimpleXMLRPCServer):
+    def __init__(self, interface):
+        SimpleXMLRPCServer.__init__(self, interface, logRequests=False, allow_none=True)
+        self.register_function(self.registerEventHandler, "registerEventHandler")
+        self.register_function(self.unregisterEventHandler, "unregisterEventHandler")
+        self.register_function(self.terminateServer, "terminateServer")
+        #self.register_function(self.runCommand, "runCommand")
+        self.quit = False
+        self.clients = {}
+        self.client_ui_ids = {}
+
+    def registerEventHandler(self, host, port):
+        """
+        Register a remote UI Event Handler
+        """
+        print "registering handler %s:%s" % (host,port) 
+        connection = xmlrpclib.ServerProxy("http://%s:%d/" % (host, port), allow_none=True)
+        client_hash = "%s:%d" % (host, port)
+        if self.clients.has_key(client_hash):
+            return None
+        client_ui_server = BitBakeUIEventServer(connection)
+        self.client_ui_ids[client_hash] = bb.event.register_UIHhandler(client_ui_server)
+        client_ui_server.start()
+        self.clients[client_hash] = client_ui_server
+        return client_hash
+
+    def unregisterEventHandler(self, client_hash):
+        """
+        Unregister a remote UI Event Handler
+        """
+        print "unregistering handler %s:%s" % (host,port)
+        client_thread = self.clients[client_hash]
+        if client_thread:
+            bb.event.unregister_UIHhandler(self.clients_ui_ids[client_hash])
+            client_thread.terminateServer()
+            client_thread.join()
+            return True
+        else:
+            return False
+
+    def terminateServer(self):
+        self.quit = True
+
+    def runCommand(self, cmd):
+        return None
+
+    def serve_forever(self, main_server):
+        self.main_server = main_server
+        while not self.quit:
+            self.handle_request()
+        self.server_close()
 
 class BitBakeXMLRPCServer(SimpleXMLRPCServer):
     # remove this when you're done with debugging
@@ -190,13 +332,15 @@ class BitBakeXMLRPCServer(SimpleXMLRPCServer):
         Constructor
         """
         SimpleXMLRPCServer.__init__(self, interface,
-                                    requestHandler=SimpleXMLRPCRequestHandler,
+                                    requestHandler=BitBakeXMLRPCRequestHandler,
                                     logRequests=False, allow_none=True)
         self._idlefuns = {}
         self.host, self.port = self.socket.getsockname()
+        self.connection_token = None
         #self.register_introspection_functions()
         self.commands = BitBakeServerCommands(self)
         self.autoregister_all_functions(self.commands, "")
+        self.interface = interface
 
     def addcooker(self, cooker):
         self.cooker = cooker
@@ -218,8 +362,16 @@ class BitBakeXMLRPCServer(SimpleXMLRPCServer):
         self._idlefuns[function] = data
 
     def serve_forever(self):
+        # Create and run the event server controller in a separate thread
+        evt_server_ctrl = BitBakeXMLRPCEventServerController((self.host, self.port + 2))
+        self.event_controller_thread = threading.Thread(target = evt_server_ctrl.serve_forever, args = (self,))
+        self.event_controller_thread.start()
+        # Start the actual XMLRPC server
         bb.cooker.server_main(self.cooker, self._serve_forever)
 
+    def removeClient(self):
+        self.commands.removeClient()
+
     def _serve_forever(self):
         """
         Serve Requests. Overloaded to honor a quit command
@@ -259,10 +411,15 @@ class BitBakeXMLRPCServer(SimpleXMLRPCServer):
                 retval = function(self, data, True)
             except:
                 pass
-
+        # Terminate the event server
+        self.event_controller_thread.terminateServer()
+        self.event_controller_thread.join()
         self.server_close()
         return
 
+    def set_connection_token(self, token):
+        self.connection_token = token
+
 class BitbakeServerInfo():
     def __init__(self, host, port):
         self.host = host
@@ -321,4 +478,41 @@ class BitBakeServer(object):
 
     def establishConnection(self):
         self.connection = BitBakeServerConnection(self.serverinfo)
-        return self.connection
+        return self.connection.connect()
+
+    def set_connection_token(self, token):
+        self.connection.transport.set_connection_token(token)
+
+    def endSession(self):
+        self.connection.terminate()
+
+class BitBakeXMLRPCClient(object):
+
+    def __init__(self):
+        pass
+
+    def saveConnectionDetails(self, remote):
+        self.remote = remote
+
+    def establishConnection(self):
+        # The format of "remote" must be "server:port"
+        try:
+            [host, port] = self.remote.split(":")
+            port = int(port)
+        except:
+            return None
+        # We need our IP for the server connection. We get the IP
+        # by trying to connect with the server
+        try:
+            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            s.connect((host, port))
+            ip = s.getsockname()[0]
+            s.close()
+        except:
+            return None
+        self.serverinfo = BitbakeServerInfo(host, port)
+        self.connection = BitBakeServerConnection(self.serverinfo, (ip, 0))
+        return self.connection.connect()
+
+    def endSession(self):
+        self.connection.removeClient()