def get_object(package_name: str, class_name: str, object_name: str, server_url: Optional[str] = None) -> dict: """Find a raw QMF2 object by type and name. Args: package_name: Qpid internal package name to query. class_name: Qpid internal class name to query. object_name: Name of the Qpid object to find. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: Raw QMF2 object. """ object_: dict = {} def handle_response(message: Message): for result in message.body: if result['_values']['name'].decode() == object_name: object_.update(result) return True return True rpc = RemoteProcedure(handle_response, 'qmf.default.direct', server_url) rpc.call(create_QMF2_query(package_name, class_name), timedelta(seconds=5)) if not object_: raise ObjectNotFound(class_name, object_name) return object_
def get_sessions(server_url: Optional[str] = None) -> dict: """Retrieve sessions from AMQP broker. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: A dict mapping between session id and it's address. Example: >>> get_sessions() {'org.apache.qpid.broker:session:0x7fb8bc021ab0': {'address': '10.0.0.2:34814'}} """ sessions = {} def _update_sessions(message: Message): for item in message.body: values = item['_values'] connectionRef = values['connectionRef']['_object_name'].decode() sessions[item['_object_id']['_object_name'].decode()] = { 'address': connectionRef.rsplit('-', 1)[1]} return True rpc = RemoteProcedure(_update_sessions, 'qmf.default.direct', server_url) binding_query_message = create_QMF2_query('org.apache.qpid.broker', 'session') rpc.call(binding_query_message, timedelta(seconds=5)) return sessions
def delete_binding(exchange_name: str, queue_name: str, binding_name: str = None, server_url: Optional[str] = None): """Delete a binding on the broker. Args: exchange_name: Name of exchange. queue_name: Name of queue. binding_name: Name of binding. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. """ rpc = RemoteProcedure(handle_QMF2_exception, 'qmf.default.direct', server_url) method_arguments = { 'type': 'binding', 'name': '{}/{}'.format(exchange_name, queue_name) } if binding_name: method_arguments['name'] = '{}/{}'.format(method_arguments['name'], binding_name) delete_binding_message = create_QMF2_method_invoke( get_broker_id(server_url), 'delete', method_arguments ) rpc.call(delete_binding_message, timedelta(seconds=5))
def create_exchange(exchange_name: str, exchange_type: ExchangeType = ExchangeType.direct, durable: bool = True, server_url: Optional[str] = None): """Create an exchange on the broker. Args: exchange_name: Exchange name. exchange_type: `direct`, `topic`, `fanout`, `headers`. durable: Persist the created exchange on broker restarts. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. """ rpc = RemoteProcedure(handle_QMF2_exception, 'qmf.default.direct', server_url) create_exchange_message = create_QMF2_method_invoke( get_broker_id(server_url), 'create', { 'type': 'exchange', 'name': exchange_name, 'properties': { 'durable': durable, 'exchange-type': exchange_type.value } } ) rpc.call(create_exchange_message, timedelta(seconds=5))
def get_outgoing_sessions_by_address( server_url: Optional[str] = None) -> MutableMapping[str, list]: """Retrieve outgoing sessions from AMQP broker. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: defaultdict: A dict mapping between address name and list of sessions. Example: >>> get_outgoing_sessions_by_address() {'8152f68b-c74a-4d22-8630-a89cf194d067_8152f68b-\ c74a-4d22-8630-a89cf194d067-2d808664-fe81-4da4-8258-288a7ff531ac': [{\ 'session_id': 'org.apache.qpid.broker:session:0x7fb8bc021ab0','transfers': \ ulong(0)}]} """ client_subscriptions: MutableMapping[str, list] = defaultdict(list) def _update_results(message: Message): for value in (item['_values'] for item in message.body): client_subscriptions[value['source'].decode()].append({ 'session_id': value['sessionRef']['_object_name'].decode(), 'transfers': value['transfers'] }) return True rpc = RemoteProcedure(_update_results, 'qmf.default.direct', server_url) binding_query_message = create_QMF2_query('org.apache.qpid.broker', 'outgoing') rpc.call(binding_query_message, timedelta(seconds=5)) return dict(client_subscriptions)
def main(): try_create_queue(QUEUE_NAME) remote_service = RemoveService() remote_service.start() message = create_message(f'Test RPC message') rpc = RemoteProcedure(rpc_callback, QUEUE_NAME, SERVER_URL) rpc.call(message, TIMEOUT) sleep(3) remote_service.receiver.stop()
def kill_connection(connection_id: dict, server_url: Optional[str] = None): """Kill connection on AMQP broker. Args: connection_id: ID of connection. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. """ rpc = RemoteProcedure(handle_QMF2_exception, 'qmf.default.direct', server_url) rpc.call(create_QMF2_method_invoke(connection_id, 'close', {}), timedelta(seconds=5))
def queue_statistics(queue_name: Optional[str] = None, include_autodelete: bool = False, server_url: Optional[str] = None) -> dict: """Retrieve total messages count and depth for all queues from AMQP broker. Args: queue_name: Name of queue. include_autodelete: Include autodelete queues to output. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: A dict mapping between queue address and dict with total messages and queue depth. Example: >>> queue_statistics(queue_name='examples') {'org.apache.qpid.broker:queue:examples': {'name': 'examples', 'total': 96, 'depth': 12}} """ queues = {} def _update_queue_stats(message: Message): for item in message.body: values = item['_values'] if values['autoDelete'] and not include_autodelete: continue # We are not interested in temp reply queues current_queue_name = values['name'].decode() if queue_name and current_queue_name != queue_name: continue queues[item['_object_id']['_object_name'].decode()] = { 'name': values['name'].decode(), 'total': int(values['msgTotalEnqueues']), 'depth': int(values['msgDepth']) } return True rpc = RemoteProcedure(_update_queue_stats, 'qmf.default.direct', server_url) queue_query_message = create_QMF2_query('org.apache.qpid.broker', 'queue') rpc.call(queue_query_message, timedelta(seconds=15)) return queues
def delete_exchange(exchange_name: str, server_url: Optional[str] = None): """Delete an exchange on the broker. Args: exchange_name: Exchange name. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. """ rpc = RemoteProcedure(handle_QMF2_exception, 'qmf.default.direct', server_url) delete_exchange_message = create_QMF2_method_invoke( get_broker_id(server_url), 'delete', { 'type': 'exchange', 'name': exchange_name } ) rpc.call(delete_exchange_message, timedelta(seconds=5))
def get_broker_id(server_url: Optional[str] = None) -> dict: """Get the full internal broker ID object. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: Full internal broker ID object. """ broker_id: dict = {} def handle_response(message: Message): broker_id.update(message.body[0]['_object_id']) return True rpc = RemoteProcedure(handle_response, 'qmf.default.direct', server_url) broker_query_message = create_QMF2_query('org.apache.qpid.broker', 'broker') rpc.call(broker_query_message, timedelta(seconds=5)) return broker_id
def create_binding(exchange_name: str, queue_name: str, binding_name=None, headers_match: dict = None, server_url: Optional[str] = None): """Create binding between queue and exchange. Args: exchange_name: Name of exchange. queue_name: Name of queue. binding_name: Name of binding. headers_match: Headers key-value pairs that should be presented on message to match the binding. Only for `headers` exchange type. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. """ exchange = get_object('org.apache.qpid.broker', 'exchange', exchange_name, server_url) if headers_match and exchange['_values']['type'].decode() != 'headers': raise RuntimeError("Headers match only supported on headers exchange") method_arguments: MutableMapping = { 'type': 'binding', 'name': '{}/{}'.format(exchange_name, queue_name), } if binding_name: method_arguments['name'] = '{}/{}'.format(method_arguments['name'], binding_name) if headers_match: method_arguments['properties'] = copy(headers_match) method_arguments['properties']['x-match'] = 'all' rpc = RemoteProcedure(handle_QMF2_exception, 'qmf.default.direct', server_url) rpc.call(create_QMF2_method_invoke(get_broker_id(server_url), 'create', method_arguments), timedelta(seconds=5))
def get_binding_keys(exchange_name: str, queue_name: str = None, server_url: Optional[str] = None ) -> Set[Tuple[str, str, str]]: """Retrieve all bindings for specified exchange. Args: exchange_name: Name of exchange. queue_name: Name of queue. server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: Set of binding keys. """ result = set() def _filter_bindings(message: Message): for item in message.body: values = item['_values'] queue_id = values['queueRef']['_object_name'].decode() qpid_queue_name = queue_id.rsplit(':', 1)[-1] exchange_id = values['exchangeRef']['_object_name'].decode() qpid_exchange_name = exchange_id.rsplit(':', 1)[-1] if exchange_name == qpid_exchange_name: if not queue_name or (queue_name == qpid_queue_name): result.add(( qpid_exchange_name, qpid_queue_name, values['bindingKey'].decode() )) return True rpc = RemoteProcedure(_filter_bindings, 'qmf.default.direct', server_url) binding_query_message = create_QMF2_query('org.apache.qpid.broker', 'binding') rpc.call(binding_query_message, timedelta(seconds=5)) return result
def exchange_statistics(server_url: Optional[str] = None) -> dict: """Retrieve total and dropped amount of messages for exchanges from AMQP broker. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: A dict mapping between exchange address and dict with exchange name, total messages count and dropped messages count. Example: >>> exchange_statistics() {'org.apache.qpid.broker:exchange:': {'name': '', 'total': ulong(236), 'dropped': ulong(0)}} """ exchanges = {} def _update_exchange_stats(message: Message): for item in message.body: values = item['_values'] name = values['name'].decode() if name.startswith(('qmf', 'qpid', 'amq')): continue # Don't export Qpid/QMF related stats exchanges[item['_object_id']['_object_name'].decode()] = { 'name': name, 'total': values['msgReceives'], 'dropped': values['msgDrops'] } return True rpc = RemoteProcedure(_update_exchange_stats, 'qmf.default.direct', server_url) exchange_query_message = create_QMF2_query('org.apache.qpid.broker', 'exchange') rpc.call(exchange_query_message, timedelta(seconds=5)) return exchanges
def get_exchange_bindings(server_url: Optional[str] = None) -> dict: """Retrieve all exchanges and bindings associated with these exchanges. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: A dict mapping between exchange it's bindings. Example: >>> get_exchange_bindings() {'org.apache.qpid.broker:exchange:': [\ {'queue_id': 'org.apache.qpid.broker:queue:examples', 'headers_match': {}}]} """ results: defaultdict = defaultdict(list) def _update_bindings(message: Message): for item in message.body: logger.info("Got binding: %s", item) values = item['_values'] exchange_id = values['exchangeRef']['_object_name'].decode() results[exchange_id].append({ 'queue_id': values['queueRef']['_object_name'].decode(), 'headers_match': values.get('arguments') }) return True rpc = RemoteProcedure(_update_bindings, 'qmf.default.direct', server_url) binding_query_message = create_QMF2_query('org.apache.qpid.broker', 'binding') rpc.call(binding_query_message, timedelta(seconds=5)) return results
def get_connection_ids(server_url: Optional[str] = None) -> list: """Retrieve connection ids of all established connections to AMQP broker. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: List of connections """ connections = [] def _update_connections(message: Message): for item in message.body: connections.append(item['_object_id']['_object_name']) return True rpc = RemoteProcedure(_update_connections, 'qmf.default.direct', server_url) binding_query_message = create_QMF2_query('org.apache.qpid.broker', 'connection') rpc.call(binding_query_message, timedelta(seconds=5)) return connections
def test_connection_error(self): rpc = RemoteProcedure(lambda m: True, uuid4().hex, server_url='amqp://*****:*****@example') with self.assertRaises(ConnectionError): rpc.call(create_message(b'FOOBAR'))
def gather_statistics(server_url: Optional[str] = None) -> dict: """Retrieve statistics about exchanges and queues from AMQP broker. Statistics data includes exchanges and queues. Exchange information includes exchange name, total and dropped amount of messages. Queue information includes messages count, depth and bindings to exchange. Args: server_url: Comma-separated list of urls to connect to. Multiple can be specified for connection fallback, the first should be the primary server. Returns: dict: Exchange and queue statistics. Example: >>> gather_statistics() {'exchanges': {'org.apache.qpid.broker:exchange:': {'dropped': \ ulong(0), 'name': '', 'total': ulong(251)}}, 'queues': {\ 'org.apache.qpid.broker:queue:examples': {'bindings': [{'exchange_id': \ 'org.apache.qpid.broker:exchange:', 'name': 'default_route', 'total': 96}], \ 'depth': 12, 'name': 'examples', 'total': 96}}} """ stats = { 'queues': queue_statistics(server_url=server_url), 'exchanges': exchange_statistics(server_url), } for queue in stats['queues'].values(): queue['bindings'] = [] def _update_binding_stats(message: Message): for item in message.body: values = item['_values'] queue_id = values['queueRef']['_object_name'].decode() exchange_id = values['exchangeRef']['_object_name'].decode() if queue_id not in stats['queues']: continue # Filtered queue if exchange_id not in stats['exchanges']: continue # Filtered exchange if exchange_id == EXCHANGE_ID_PREFIX: continue # Default exchange stats are broken, reconstruct exchange_stats = { 'name': values['bindingKey'].decode(), 'exchange_id': exchange_id, 'total': values['msgMatched'] } stats['queues'][queue_id]['bindings'].append(exchange_stats) return True rpc = RemoteProcedure(_update_binding_stats, 'qmf.default.direct', server_url) binding_query_message = create_QMF2_query('org.apache.qpid.broker', 'binding') rpc.call(binding_query_message, timedelta(seconds=15)) # Reconstruct default route stats for queue in stats['queues'].values(): total_routed = sum((binding['total'] for binding in queue['bindings'])) queue['bindings'].append({ 'name': 'default_route', 'exchange_id': EXCHANGE_ID_PREFIX, 'total': queue['total'] - total_routed }) return stats