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