def call_method(self, name, method, params): """Call a previously registered subsystem method by name. Only methods tagged with the @api_call decorator can be called. :param name: Assigned name of the registered subsystem. :type name: string :param method: Subsystem method to be called. :type method: string :param params: Additional parameters for the called method. :type params: dict :returns: call_reply or error message dict to be sent to caller. """ self.logger.debug("API call: {}.{}({})".format(name, method, params)) if name in self.systems: obj = self.systems[name] if is_api_method(obj, method): try: # Calls given obj.method, unpacking and passing params dict call_return = getattr(obj, method)(**params) msg = "Called {}.{}".format(name, method) self.logger.debug(msg + ",returned:{}".format(call_return)) return msgs.call_reply(msg, call_return) except TypeError: # Raised when we have a mismatch of the method's kwargs # TODO: Return argspec here? err_msg = "Invalid params for {}.{}".format(name, method) self.logger.warning(err_msg) return msgs.error(err_msg) except Exception as e: # Catch exception raised by called method, notify client err_msg = "Exception: '{}'".format(str(e)) self.logger.warning(err_msg) return msgs.error(err_msg) else: err_msg = "Invalid method: '{}.{}'".format(name, method) self.logger.warning(err_msg) return msgs.error(err_msg) else: err_msg = "Invalid object: '{}'".format(name) self.logger.warning(err_msg) return msgs.error(err_msg)
def handle_msg(self, msg): """Generic message handler. Hands-off based on type of message. :param msg: Message, received via ZMQ from client, to handle. :type msg: dict :returns: An appropriate message reply dict, from lib.messages. """ self.logger.debug("Received: {}".format(msg)) try: msg_type = msg["type"] except KeyError as e: return msgs.error(e) if msg_type == "ping_req": reply = msgs.ping_reply() elif msg_type == "list_req": reply = self.list_callables() elif msg_type == "call_req": try: obj_name = msg["obj_name"] method = msg["method"] params = msg["params"] reply = self.call_method(obj_name, method, params) except KeyError as e: return msgs.error(e) elif msg_type == "exit_req": self.logger.info("Received message to die. Bye!") reply = msgs.exit_reply() # Need to actually send reply here as we're about to exit self.logger.debug("Sending: {}".format(reply)) self.ctrl_sock.send_json(reply) self.clean_up() sys.exit(0) else: err_msg = "Unrecognized message: {}".format(msg) self.logger.warning(err_msg) reply = msgs.error(err_msg) return reply
def listen(self): """Perpetually listen for messages, pass them to generic handler.""" self.logger.info("Control server: {}".format(self.server_bind_addr)) while True: try: msg = self.ctrl_sock.recv_json() reply = self.handle_msg(msg) self.logger.debug("Sending: {}".format(reply)) self.ctrl_sock.send_json(reply) except JSONDecodeError: err_msg = "Not a JSON message!" self.logger.warning(err_msg) self.ctrl_sock.send_json(msgs.error(err_msg)) except KeyboardInterrupt: self.logger.info("Exiting control server. Bye!") self.clean_up() sys.exit(0)