#!/usr/bin/env python # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # """ This module implements a simple RPC client. The client sends a 'method call' to a server, and waits for a response. The method call is a map of the form: {'method': '', 'args': { 0: readfd = [self.socket] if self.connection.has_output > 0: writefd = [self.socket] timeout = None deadline = self.connection.next_tick if deadline: now = time.time() timeout = 0 if deadline <= now else deadline - now LOG.debug("select() start (t=%s)", str(timeout)) readable, writable, ignore = select.select(readfd, writefd, [], timeout) LOG.debug("select() returned") if readable: try: pyngus.read_socket_input(self.connection, self.socket) except Exception as e: LOG.error("Exception on socket read: %s", str(e)) self.connection.close_input() self.connection.close() self.connection.process(time.time()) if writable: try: pyngus.write_socket_output(self.connection, self.socket) except Exception as e: LOG.error("Exception on socket write: %s", str(e)) self.connection.close_output() self.connection.close() def close(self, error=None): self.connection.close(error) @property def closed(self): return self.connection.closed def destroy(self, error=None): self.reset() self.caller.destroy() self.caller = None self.container = None def create_caller(self, method_map, source_addr, target_addr, receiver_properties, sender_properties): """ Caller factory """ if self.caller: self.caller.destroy() self.caller = MyCaller(method_map, self, source_addr, target_addr, receiver_properties, sender_properties) return self.caller # Connection callbacks: def connection_active(self, connection): """connection handshake completed""" LOG.debug("Connection active callback") def connection_remote_closed(self, connection, reason): LOG.debug("Connection remote closed callback") connection.close() def connection_closed(self, connection): LOG.debug("Connection closed callback") def connection_failed(self, connection, error): LOG.error("Connection failed! error=%s", str(error)) raise Exception("Connection failure: %s" % str(error)) def sender_requested(self, connection, link_handle, name, requested_source, properties): # call accept_sender to accept new link, # reject_sender to reject it. assert False, "Not expected" def receiver_requested(self, connection, link_handle, name, requested_target, properties): # call accept_sender to accept new link, # reject_sender to reject it. assert False, "Not expected" def sasl_done(self, connection, pn_sasl, result): LOG.debug("SASL done, result=%s", str(result)) class MyCaller(pyngus.SenderEventHandler, pyngus.ReceiverEventHandler): """Implements state for a single RPC call.""" def __init__(self, method_map, my_connection, my_source, my_target, receiver_properties, sender_properties): # self._name = uuid.uuid4().hex self._my_connection = my_connection self._source_addr = my_source self._target_addr = my_target self._receiver_properties = receiver_properties self._sender_properties = sender_properties self._method = method_map self._sender = None self._receiver = None self.reset() def reset(self): LOG.debug("Resetting my-caller") # TODO(kgiusti: for now, use new name as engine isn't cleaning up link # state properly... self._name = uuid.uuid4().hex self._reply_to = None self._to = None self._send_completed = False self._response_received = False if self._sender: self._sender.destroy() self._sender = None if self._receiver: self._receiver.destroy() self._receiver = None def connect(self): self.reset() LOG.debug("Connecting my-caller") conn = self._my_connection.connection self._sender = conn.create_sender( self._source_addr, target_address=None, event_handler=self, # name=self._source_addr, name=self._name, properties=self._sender_properties) self._receiver = conn.create_receiver( self._target_addr, source_address=None, event_handler=self, # name=self._target_addr, name=self._name, properties=self._receiver_properties) self._sender.open() self._receiver.add_capacity(1) self._receiver.open() def done(self): return self._send_completed and self._response_received def close(self): LOG.debug("Closing my-caller") self._sender.close(None) self._receiver.close(None) def closed(self): return self._sender.closed and self._receiver.closed def destroy(self): LOG.debug("Destroying my-caller") self.reset() self._my_connection = None def _send_request(self): """Send a message containing the RPC method call """ msg = Message() msg.subject = "An RPC call!" msg.address = self._to msg.reply_to = self._reply_to msg.body = self._method msg.correlation_id = 5 # whatever... print("sending RPC call request: %s" % str(self._method)) # @todo send timeout self._sender.send(msg, self, None, time.time() + # 10) self._sender.send(msg, self) # SenderEventHandler callbacks: def sender_active(self, sender_link): LOG.debug("Sender active callback") assert sender_link is self._sender self._to = sender_link.target_address assert self._to, "Expected a target address!!!" if self._reply_to: self._send_request() def sender_remote_closed(self, sender_link, error): LOG.debug("Sender remote closed callback") assert sender_link is self._sender self._sender.close() def sender_closed(self, sender_link): LOG.debug("Sender closed callback") assert sender_link is self._sender self._send_completed = True # send complete callback: def __call__(self, sender_link, handle, status, error): LOG.debug("Sender message sent callback, status=%s", str(status)) self._send_completed = True # ReceiverEventHandler callbacks: def receiver_active(self, receiver_link): LOG.debug("Receiver active callback") assert receiver_link is self._receiver self._reply_to = receiver_link.source_address assert self._reply_to, "Expected a source address!!!" if self._to: self._send_request() def receiver_remote_closed(self, receiver_link, error): LOG.debug("receiver remote closed callback") assert receiver_link is self._receiver self._receiver.close() def receiver_closed(self, receiver_link): LOG.debug("Receiver closed callback") assert receiver_link is self._receiver self._response_received = True def message_received(self, receiver_link, message, handle): LOG.debug("Receiver message received callback") assert receiver_link is self._receiver print("RPC call response received: %s" % str(message)) receiver_link.message_accepted(handle) self._response_received = True def main(argv=None): _usage = """Usage: %prog [options] method arg1 value1 [arg2 value2] ...""" parser = optparse.OptionParser(usage=_usage) parser.add_option("-a", dest="server", type="string", default="amqp://0.0.0.0:5672", help="The address of the server [amqp://0.0.0.0:5672]") parser.add_option("-t", "--timeout", dest="timeout", type="int", help="timeout used when waiting for reply, in seconds") parser.add_option("--repeat", dest="repeat", type="int", default=1, help="Repeat the RPC call REPEAT times (0 == forever)") parser.add_option("--trace", dest="trace", action="store_true", help="enable protocol tracing") parser.add_option("--debug", dest="debug", action="store_true", help="enable debug logging") parser.add_option("--ca", help="Certificate Authority PEM file") opts, method_info = parser.parse_args(args=argv) if not method_info: assert False, "No method info specified!" if len(method_info) % 2 != 1: assert False, "An even number of method arguments are required!" if opts.debug: LOG.setLevel(logging.DEBUG) # Create a socket connection to the server # regex = re.compile(r"^amqp://([a-zA-Z0-9.]+)(:([\d]+))?$") LOG.debug("Connecting to %s", opts.server) x = regex.match(opts.server) if not x: raise Exception("Bad address syntax: %s" % opts.server) matches = x.groups() host = matches[0] port = int(matches[2]) if matches[2] else None addr = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM) if not addr: raise Exception("Could not translate address '%s'" % opts.server) my_socket = socket.socket(addr[0][0], addr[0][1], addr[0][2]) my_socket.setblocking(0) # 0=non-blocking try: my_socket.connect(addr[0][4]) except socket.error as e: if e.errno != errno.EINPROGRESS: raise # create AMQP container, connection, sender and receiver # container = pyngus.Container(uuid.uuid4().hex) conn_properties = {} if opts.trace: conn_properties["x-trace-protocol"] = True if opts.ca: conn_properties["x-ssl-ca-file"] = opts.ca my_connection = MyConnection("to-server", container, conn_properties) # Create the RPC caller method = {'method': method_info[0], 'args': dict([(method_info[i], method_info[i + 1]) for i in range(1, len(method_info), 2)])} my_caller = my_connection.create_caller(method, "my-source-address", "my-target-address", receiver_properties={}, sender_properties={}) try: my_connection.connect(my_socket) repeat = 0 while opts.repeat == 0 or repeat < opts.repeat: LOG.debug("Requesting RPC...") my_caller.connect() while not my_caller.done(): my_connection.process() LOG.debug("RPC completed! Closing caller...") my_caller.close() while not my_caller.closed(): my_connection.process() LOG.debug("Caller closed cleanly!") repeat += 1 print("Closing connections") my_connection.close() while not my_connection.closed: my_connection.process() LOG.debug("Connection closed") my_caller.destroy() my_connection.destroy() except Exception as e: LOG.error("Unexpected exception occured: %s", str(e)) return -1 return 0 if __name__ == "__main__": sys.exit(main())