示例#1
0
   def start_guest(self, config):
      """
      Start a new guest process on this node.

      :param config: The guest process configuration.
      :type config: obj

      :returns: int -- The PID of the new process.
      """

      class GuestClientProtocol(protocol.ProcessProtocol):

         def __init__(self):
            self._pid = None
            self._name = None

         def _log(self, data):
            for msg in data.split('\n'):
               msg = msg.strip()
               if msg != "":
                  log.msg(msg, system = "{:<10} {:>6}".format(self._name, self._pid))

         def connectionMade(self):
            if 'stdout' in config and config['stdout'] == 'close':
               self.transport.closeStdout()

            if 'stderr' in config and config['stderr'] == 'close':
               self.transport.closeStderr()

            if 'stdin' in config:
               if config['stdin'] == 'close':
                  self.transport.closeStdin()
               else:
                  if config['stdin']['type'] == 'json':
                     self.transport.write(json.dumps(config['stdin']['value']))
                  elif config['stdin']['type'] == 'msgpack':
                     pass ## FIXME
                  else:
                     raise Exception("logic error")

                  if config['stdin'].get('close', True):
                     self.transport.closeStdin()

         def outReceived(self, data):
            if config.get('stdout', None) == 'log':
               self._log(data)

         def errReceived(self, data):
            if config.get('stderr', None) == 'log':
               self._log(data)

         def inConnectionLost(self):
            pass

         def outConnectionLost(self):
            pass

         def errConnectionLost(self):
            pass

         def processExited(self, reason):
            pass

         def processEnded(self, reason):
            if isinstance(reason.value,  ProcessDone):
               log.msg("Guest {}: Ended cleanly.".format(self._pid))
            elif isinstance(reason.value, ProcessTerminated):
               log.msg("Guest {}: Ended with error {}".format(self._pid, reason.value.exitCode))
            else:
               ## should not arrive here
               pass


      class GuestClientFactory(protocol.Factory):

         protocol = GuestClientProtocol


      exe = config['executable']

      args = [exe]
      args.extend(config.get('arguments', []))

      workdir = self._node._cbdir
      if 'workdir' in config:
         workdir = os.path.join(workdir, config['workdir'])
      workdir = os.path.abspath(workdir)

      ready = Deferred()
      exit = Deferred()


      if False:
         self._guest_no += 1

         factory = GuestClientFactory()

         ep = CustomProcessEndpoint(self._node._reactor,
                  exe,
                  args,
                  name = "Guest {}".format(self._guest_no),
                  env = os.environ)

         ## now actually spawn the worker ..
         ##
         d = ep.connect(factory)

         def onconnect(proto):
            pid = proto.transport.pid
            proto._pid = pid
            self._guests[pid] = GuestProcess(pid, ready, exit, proto = proto)
            log.msg("Guest {}: Program started.".format(pid))
            ready.callback(pid)

         def onerror(err):
            log.msg("Guest: Program could not be started - {}".format(err.value))
            ready.errback(err)

         d.addCallbacks(onconnect, onerror)

      else:
         self._guest_no += 1

         proto = GuestClientProtocol()
         proto._name = "Guest {}".format(self._guest_no)

         try:
            trnsp = self._node._reactor.spawnProcess(proto, exe, args, path = workdir, env = os.environ)
         except Exception as e:
            log.msg("Guest: Program could not be started - {}".format(e))
            ready.errback(e)
         else:
            pid = trnsp.pid
            proto._pid = pid
            self._guests[pid] = GuestProcess(pid, ready, exit, proto = proto)
            log.msg("Guest {}: Program started.".format(pid))
            ready.callback(pid)

      return ready
示例#2
0
   def start_worker(self, title = None, debug = False):
      """
      Start a new Crossbar.io worker process.

      :param title: Optional process title to set.
      :type title: str
      :param debug: Enable debugging on this worker.
      :type debug: bool

      :returns: int -- The PID of the new worker process.
      """

      ## all worker processes start "generic" (like stem cells) and
      ## are later configured via WAMP from the node controller session
      ##
      filename = pkg_resources.resource_filename('crossbar', 'worker/process.py')

      args = [executable, "-u", filename]
      args.extend(["--cbdir", self._node._cbdir])

      if title:
         args.extend(['--name', title])

      if debug or self.debug:
         args.append('--debug')

      self._worker_no += 1

      ep = CustomProcessEndpoint(self._node._reactor,
               executable,
               args,
               name = "Worker {}".format(self._worker_no),
               env = os.environ)

      ## this will be resolved/rejected when the worker is actually
      ## ready to receive commands
      ready = Deferred()

      ## this will be resolved when the worker exits (after previously connected)
      exit = Deferred()


      class WorkerClientProtocol(WampWebSocketClientProtocol):

         def connectionMade(self):
            WampWebSocketClientProtocol.connectionMade(self)
            self._pid = self.transport.pid
            self.factory.proto = self


         def connectionLost(self, reason):
            WampWebSocketClientProtocol.connectionLost(self, reason)
            self.factory.proto = None

            log.msg("Worker {}: Process connection gone ({})".format(self._pid, reason.value))

            if isinstance(reason.value, ProcessTerminated):
               if not ready.called:
                  ## the worker was never ready in the first place ..
                  ready.errback(reason)
               else:
                  ## the worker _did_ run (was ready before), but now exited with error
                  if not exit.called:
                     exit.errback(reason)
                  else:
                     log.msg("FIXME: unhandled code path (1) in WorkerClientProtocol.connectionLost", reason.value)
            elif isinstance(reason.value, ProcessDone) or isinstance(reason.value, ConnectionDone):
               ## the worker exited cleanly
               if not exit.called:
                  exit.callback()
               else:
                  log.msg("FIXME: unhandled code path (2) in WorkerClientProtocol.connectionLost", reason.value)
            else:
               ## should not arrive here
               log.msg("FIXME: unhandled code path (3) in WorkerClientProtocol.connectionLost", reason.value)


      class WorkerClientFactory(WampWebSocketClientFactory):

         def __init__(self, *args, **kwargs):
            WampWebSocketClientFactory.__init__(self, *args, **kwargs)
            self.proto = None

         def buildProtocol(self, addr):
            self.proto = WorkerClientProtocol()
            self.proto.factory = self
            return self.proto

         def stopFactory(self):
            WampWebSocketClientFactory.stopFactory(self)
            if self.proto:
               self.proto.close()
               #self.proto.transport.loseConnection()


      ## factory that creates router session transports. these are for clients
      ## that talk WAMP-WebSocket over pipes with spawned worker processes and
      ## for any uplink session to a management service
      ##
      factory = WorkerClientFactory(self._node._router_session_factory, "ws://localhost", debug = self.debug)

      ## we need to increase the opening handshake timeout in particular, since starting up a worker
      ## on PyPy will take a little (due to JITting)
      factory.setProtocolOptions(failByDrop = False, openHandshakeTimeout = 30, closeHandshakeTimeout = 5)

      ## now actually spawn the worker ..
      ##
      d = ep.connect(factory)

      def onconnect(res):
         pid = res.transport.pid
         log.msg("Worker PID {} process connected".format(pid))

         ## remember the worker process, including "ready" deferred. this will later
         ## be fired upon the worker publishing to 'crossbar.node.{}.on_worker_ready'
         self._workers[pid] = WorkerProcess(pid, ready, exit, factory = factory)

      def onerror(err):
         log.msg("Could not start worker process with args '{}': {}".format(args, err.value))
         ready.errback(err)

      d.addCallbacks(onconnect, onerror)

      return ready