예제 #1
0
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_
예제 #2
0
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
예제 #3
0
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)
예제 #4
0
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))
예제 #5
0
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))
예제 #6
0
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()
예제 #7
0
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))
예제 #8
0
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
예제 #9
0
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))
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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))
예제 #13
0
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
예제 #14
0
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
예제 #15
0
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
예제 #16
0
 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'))
예제 #17
0
 def test_reconnect_strategy_backoff_warning(self):
     with pytest.warns(UserWarning):
         RemoteProcedure(lambda m: True,
                         uuid4().hex,
                         reconnect_strategy=ReconnectStrategy.backoff)
예제 #18
0
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