Converted AMQPAdapeter Java code to Python
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import Set
|
||||
|
||||
from amqp.model.model import AMQRoute
|
||||
from amqp.router.utils import sanitize_as_rabbitmq_name
|
||||
|
||||
|
||||
class RouteDatabase:
|
||||
def __init__(self):
|
||||
self.routes: Set[AMQRoute] = set()
|
||||
|
||||
def get_routes(self) -> Set[AMQRoute]:
|
||||
return self.routes
|
||||
|
||||
def pattern(self, routing_key: str) -> re.Pattern:
|
||||
regex = r"\.".join(
|
||||
[
|
||||
r".*" if token == "" else r"#" if token == "#" else token
|
||||
for token in routing_key.split(".")
|
||||
]
|
||||
)
|
||||
if routing_key.endswith("."):
|
||||
regex += r"\."
|
||||
return re.compile(regex)
|
||||
|
||||
def matches(self, routing_key_from_route: str, routing_key_from_message: str) -> bool:
|
||||
route_pattern = self.pattern(routing_key_from_route)
|
||||
return bool(route_pattern.match(sanitize_as_rabbitmq_name(routing_key_from_message)))
|
||||
|
||||
def wrap_routes(self) -> str:
|
||||
return ",".join(str(route) for route in self.routes)
|
||||
|
||||
def find_route(self, domain: str, path: str) -> AMQRoute | None:
|
||||
routing_key = sanitize_as_rabbitmq_name(f"{domain}.{path}")
|
||||
logging.info(f" [DBG] findRoute key:{routing_key} in:{self.wrap_routes()}")
|
||||
for route in self.routes:
|
||||
if self.matches(route.key, routing_key):
|
||||
return route
|
||||
return None
|
||||
|
||||
def add_route(self, route: AMQRoute) -> bool:
|
||||
if route not in self.routes:
|
||||
self.routes.add(route)
|
||||
return True
|
||||
return False
|
||||
|
||||
def remove_route(self, route: AMQRoute) -> bool:
|
||||
return route in self.routes and self.routes.remove(route)
|
||||
@@ -0,0 +1,91 @@
|
||||
import logging
|
||||
from typing import Callable, List, Set
|
||||
|
||||
from amqp.adapter.amq_route_factory import AMQRouteFactory, NULL_ROUTE
|
||||
from amqp.model.model import AMQRoute, AMQMessage
|
||||
from amqp.router.route_database import RouteDatabase
|
||||
from amqp.router.utils import sanitize_as_rabbitmq_name
|
||||
|
||||
CM_REQUEST_EXCHANGE = "CM-Request"
|
||||
CM_RESPONSE_EXCHANGE = "CM-Response"
|
||||
CM_C_REQ_QUEUE = "C-REQ"
|
||||
CM_C_RESP_QUEUE = "C-RESP"
|
||||
CM_C_AMQ_QUEUE = "C-AMQ"
|
||||
CM_P_RESP_QUEUE = "P-RESP"
|
||||
CM_P_REPLY_TO = "P-REPLY-TO"
|
||||
ANY_RECIPIENT = "*"
|
||||
AMQ_RPC_EXCHANGE = "AMQ-RPC"
|
||||
AMQ_REPLY_TO_EXCHANGE = "AMQ-REPLY-TO"
|
||||
DLQ_EXCHANGE = "DLX"
|
||||
|
||||
|
||||
class RouterBase:
|
||||
def __init__(self):
|
||||
self.route_database = RouteDatabase()
|
||||
self.own_routes: Set[AMQRoute] = set()
|
||||
self.route_added_notifier: Callable[[AMQRoute], None] | None = None
|
||||
self.route_removed_notifier: Callable[[AMQRoute], None] | None = None
|
||||
|
||||
def get_routing_key(self, message: AMQMessage) -> str:
|
||||
return sanitize_as_rabbitmq_name(f"{message.domain}.{message.path}")
|
||||
|
||||
def wrap_routes(self) -> str:
|
||||
return self.route_database.wrap_routes()
|
||||
|
||||
def wrap_own_routes(self) -> str:
|
||||
return ",".join(str(route) for route in self.own_routes)
|
||||
|
||||
def unwrap_route_list(self, route_string: str) -> List[AMQRoute]:
|
||||
logging.info(" [DBG] RouterMaster.unwrapRoutes, route(s): %s", route_string)
|
||||
routes = [AMQRouteFactory.from_string(route_str) for route_str in route_string.split(",")]
|
||||
return [route for route in routes if route != NULL_ROUTE]
|
||||
|
||||
def get_routes(self) -> Set[AMQRoute]:
|
||||
return self.route_database.get_routes()
|
||||
|
||||
def find_route(self, domain: str, path: str) -> AMQRoute | None:
|
||||
return self.route_database.find_route(domain, path)
|
||||
|
||||
def find_route_by_message(self, message: AMQMessage) -> AMQRoute | None:
|
||||
return self.route_database.find_route(message.domain, message.path)
|
||||
|
||||
def add_routes(self, message: str) -> None:
|
||||
try:
|
||||
amq_route_list = self.unwrap_route_list(message)
|
||||
logging.info(" [DBG] RouterMaster.addRoute [%d]: %s", len(amq_route_list), message)
|
||||
for route in amq_route_list:
|
||||
logging.info(" [*] RouterMaster.addRoute, adding route: %s", route)
|
||||
if not route.exchange and not route.queue and not route.key:
|
||||
logging.error("Cannot setup AMQ Exchange for '%s', at least one of "
|
||||
"Exchange, Queue and/or Key must be present in the routing string. "
|
||||
"Expected format is '%s'", route, AMQRoute.FORMAT)
|
||||
else:
|
||||
if route.exchange != DLQ_EXCHANGE:
|
||||
is_new_route = self.route_database.add_route(route)
|
||||
if is_new_route and self.route_added_notifier:
|
||||
self.route_added_notifier(route)
|
||||
except Exception as err:
|
||||
logging.error(" [E] add_routes error: %s", err)
|
||||
|
||||
def set_route_added_notifier(self, callback: Callable[[AMQRoute], None]):
|
||||
self.route_added_notifier = callback
|
||||
|
||||
def remove_routes(self, message: str) -> None:
|
||||
try:
|
||||
to_remove = [AMQRouteFactory.from_string(route_str) for route_str in message.split(",")]
|
||||
removed_count = sum(1 for route in to_remove if self.remove_route(route))
|
||||
print(f" [*] RouterMaster.removeRoutes(): {message}, removed={removed_count}")
|
||||
except IOError as err:
|
||||
print(f" [E] Can't remove route(s): {err}")
|
||||
|
||||
def remove_route(self, route: AMQRoute) -> bool:
|
||||
is_route_removed = self.route_database.remove_route(route)
|
||||
if is_route_removed and self.route_removed_notifier:
|
||||
self.route_removed_notifier(route)
|
||||
return is_route_removed
|
||||
|
||||
def add_route(self, route: AMQRoute) -> None:
|
||||
self.route_database.add_route(route)
|
||||
|
||||
def add_own_route(self, route: AMQRoute) -> None:
|
||||
self.own_routes.add(route)
|
||||
@@ -0,0 +1,143 @@
|
||||
import logging
|
||||
from typing import Set
|
||||
|
||||
from pika import spec
|
||||
from pika.adapters.blocking_connection import BlockingChannel
|
||||
|
||||
from amqp.adapter.amq_route_factory import AMQRouteFactory
|
||||
from amqp.config.amq_configuration import AMQConfiguration
|
||||
from amqp.model.model import CleverMicroServiceMessage, ServiceMessageType, AMQRoute
|
||||
from amqp.router.router_base import CM_C_REQ_QUEUE, CM_REQUEST_EXCHANGE, CM_RESPONSE_EXCHANGE, ANY_RECIPIENT
|
||||
from amqp.router.router_producer import RouterProducer
|
||||
|
||||
|
||||
class RouterConsumer(RouterProducer):
|
||||
"""
|
||||
* Contains Consumer logic. The logic here:
|
||||
* Sets a listener on CM_C_REQ_QUEUE queue to receive routing data requests
|
||||
* Upon receipt of routing data request, responds on CM_RESPONSE_EXCHANGE with local routing data.
|
||||
* It also proactively pushes the local routing data to CM_RESPONSE_EXCHANGE on startup to
|
||||
* notify the other (excluding self) Producers about its presence.
|
||||
*
|
||||
* Consumer is also Producer (Consumer sits alongside the CM Service, and anytime the Service needs
|
||||
* to communicate with other Service, it can do so via the Adapter). Therefore, the Producer
|
||||
* capability is also required.
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: AMQConfiguration):
|
||||
super().__init__(configuration)
|
||||
self.amqRouteFactory = AMQRouteFactory()
|
||||
logging.info(" [*] RouterConsumer.<init>")
|
||||
|
||||
def init_routing_paths_consumer(self, _channel: BlockingChannel):
|
||||
"""
|
||||
* Initialize router according to Consumer mode.
|
||||
"""
|
||||
|
||||
self.init_routing_paths(_channel)
|
||||
logging.info(" [*] RouterConsumer.initConsumerRouter, channel open::", self.is_open(_channel))
|
||||
if self.is_open(_channel):
|
||||
try:
|
||||
# 1. declare a Queue that gives the RoutingDAta requests
|
||||
cm_request_queue: str = self.amq_configuration.amq_adapter.adapter_prefix() + CM_C_REQ_QUEUE
|
||||
self.channel.queue_declare(cm_request_queue, durable=True, exclusive=False, auto_delete=False)
|
||||
# 1a. bind queue to CM_RESPONSE_EXCHANGE w/o any conditions (empty routing key)
|
||||
self.channel.queue_bind(queue=cm_request_queue, exchange=CM_REQUEST_EXCHANGE, routing_key="")
|
||||
# 1b. Service adapter mode - listens on Request queue and replies to Response queue
|
||||
logging.info(" [*] RouterConsumer listens for RouteDataRequests on:", cm_request_queue)
|
||||
self.channel.basic_consume(queue=cm_request_queue,
|
||||
auto_ack=False,
|
||||
on_message_callback=self.handle_routing_data_request_message)
|
||||
except Exception as ioe:
|
||||
logging.error(" [E] RouterConsumer FATAL: cannot initialize, %s", ioe)
|
||||
else:
|
||||
logging.error(" [E] RouterConsumer FATAL: cannot initialize, RabbitMQ channel is not open.")
|
||||
|
||||
"""
|
||||
* ServiceMessageType.ROUTING_DATA_REQUEST handler listening on queue 'CleverMicroRequest'
|
||||
"""
|
||||
|
||||
def handle_routing_data_request_message(self,
|
||||
ch: BlockingChannel,
|
||||
method: spec.Basic.Deliver,
|
||||
properties: spec.BasicProperties,
|
||||
body: bytes):
|
||||
# some debug statements, TODO - remove for prod release
|
||||
delivery_tag = method.delivery_tag
|
||||
debug = "Delivery.Properties = " + properties.__str__()
|
||||
logging.info(" [DBG] ******[%] (ROUTING_DATA_REQ) tag: %s, env: %s, props: %s",
|
||||
delivery_tag, method.consumer_tag, method.get_properties(), debug)
|
||||
|
||||
self.channel.basic_ack(delivery_tag, False)
|
||||
|
||||
message: CleverMicroServiceMessage = self.service_message_factory.from_bytes(body)
|
||||
route_mapping: str = self.wrap_own_routes()
|
||||
logging.info(" [DBG] ******[%s] (ROUTING_DATA_REQ) msg=%, routeMap=[{]",
|
||||
delivery_tag, self.service_message_factory.to_string(message), route_mapping)
|
||||
# Reply only if self adapter has own routeMapping (e.g. API-gateway-side adapter has none)
|
||||
if message.is_valid() and message.message_type == ServiceMessageType.ROUTING_DATA_REQUEST and route_mapping:
|
||||
try:
|
||||
service_message: CleverMicroServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA, route_mapping, message.reply_to
|
||||
)
|
||||
self.publish_service_message(service_message, CM_RESPONSE_EXCHANGE)
|
||||
except Exception as err:
|
||||
logging.error(" [E] ******[%s] (ROUTING_DATA_REQ) Response to queue: %s failed: %s",
|
||||
delivery_tag, CM_RESPONSE_EXCHANGE, err)
|
||||
|
||||
else:
|
||||
logging.error(" [E] ******[%s] (ROUTING_DATA_REQ) [%s] produces invalid or no CM msg type.",
|
||||
delivery_tag, str(body))
|
||||
|
||||
def register_route(self, route: AMQRoute):
|
||||
"""
|
||||
* Called in Consumer (Service Adapter) context. Registers the routing data for self Service
|
||||
* with the Master router by sending own routing data to Master via CleverMicroResponse queue.
|
||||
* :param route: route to register with Master
|
||||
"""
|
||||
try:
|
||||
if self.is_open(self.channel) and route.queue:
|
||||
serviceMessage: CleverMicroServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA, route.as_string(), ANY_RECIPIENT
|
||||
)
|
||||
self.publish_service_message(serviceMessage, CM_RESPONSE_EXCHANGE)
|
||||
self.add_own_route(route)
|
||||
logging.info(" [DBG] RouterConsumer registered Route::", route)
|
||||
else:
|
||||
logging.warning(" [W] RouterConsumer couldn't register route data: %s channel isn't open.",
|
||||
CM_RESPONSE_EXCHANGE)
|
||||
except Exception as ioe:
|
||||
logging.error(" [E] RouterConsumer failed register route data: %s", ioe)
|
||||
|
||||
def remove_route_on_cleanup(self, route: AMQRoute) -> bool:
|
||||
"""
|
||||
* Unregister / remove route from the list
|
||||
* :param route: route to remove
|
||||
* :return result of remove operation
|
||||
"""
|
||||
logging.info(" [DBG] RouterConsumer.removeRoute, route::", route.as_string())
|
||||
try:
|
||||
service_message: CleverMicroServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA_REMOVE,
|
||||
route.as_string(),
|
||||
ANY_RECIPIENT
|
||||
)
|
||||
if not self.publish_service_message(service_message, CM_RESPONSE_EXCHANGE):
|
||||
logging.warning(" [W] RouterConsumer couldn't remove current route: %s, channel not open.",
|
||||
route)
|
||||
|
||||
except Exception as ioe:
|
||||
logging.error(" [E] RouterConsumer failed remove current route: %s", ioe)
|
||||
|
||||
result: bool = self.remove_route(route)
|
||||
return result
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
* Cleanup - de-register routes with master.
|
||||
"""
|
||||
routes: Set[AMQRoute] = self.get_routes()
|
||||
logging.info(" [DBG] RouterConsumer.cleanup() [%d]: %s", len(routes), self.wrap_routes())
|
||||
not_removed = sum(1 for r in (self.remove_route_on_cleanup(route) for route in routes) if not r)
|
||||
if not_removed > 0:
|
||||
logging.info(" [W] %d route(s) not removed. self may lead to failed delivery on self route", not_removed)
|
||||
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
* Maps the request to RabbitMQ exchange. Handles the service discovery logic.
|
||||
*
|
||||
* Router can be either in 'Master' or 'Service (Consumer)' modes. 'Master' mode is the outward
|
||||
* facing side that receives the requests and routes it to queue associated with requested service.
|
||||
* 'Service' mode is consumer side for given service, which receives the message from AMQ and
|
||||
* forwards it to the application.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import pika
|
||||
from pika import BasicProperties, spec
|
||||
from pika.adapters.blocking_connection import BlockingChannel
|
||||
from pika.exchange_type import ExchangeType
|
||||
|
||||
from amqp.adapter.service_message_factory import CleverMicroServiceMessageFactory
|
||||
from amqp.config.amq_configuration import AMQConfiguration
|
||||
from amqp.model.model import CleverMicroServiceMessage, ServiceMessageType
|
||||
from amqp.router.router_base import RouterBase, CM_REQUEST_EXCHANGE, CM_RESPONSE_EXCHANGE, CM_C_AMQ_QUEUE, \
|
||||
CM_P_RESP_QUEUE, CM_P_REPLY_TO, ANY_RECIPIENT
|
||||
|
||||
|
||||
class RouterProducer(RouterBase):
|
||||
|
||||
def __init__(self, configuration: AMQConfiguration):
|
||||
"""
|
||||
* Initialize router - need to supply the config values and RabbitMQ channel.
|
||||
* :param configuration: adapter's configuration reference.
|
||||
"""
|
||||
super().__init__()
|
||||
self.connection = None
|
||||
self.service_message_factory = CleverMicroServiceMessageFactory(configuration.amq_adapter.service_name)
|
||||
self.amq_configuration: AMQConfiguration = configuration
|
||||
self.channel: pika.adapters.blocking_connection.BlockingChannel | None = None
|
||||
|
||||
logging.info(f" [*] RouterProducer.<init>: %s, route: %s",
|
||||
self.amq_configuration.amq_adapter.service_name,
|
||||
self.amq_configuration.amq_adapter.route_mapping)
|
||||
|
||||
def amq_consumer(self):
|
||||
credentials = pika.PlainCredentials(
|
||||
os.environ['RABBIT_MQ_USER'], os.environ['RABBIT_MQ_PASSWORD']
|
||||
)
|
||||
self.connection = pika.BlockingConnection(
|
||||
pika.ConnectionParameters(host='localhost', credentials=credentials)
|
||||
)
|
||||
self.channel = self.connection.channel()
|
||||
self.channel.start_consuming()
|
||||
|
||||
def init_routing_paths(self, channel: pika.adapters.blocking_connection.BlockingChannel | None):
|
||||
"""
|
||||
* Initialize router according to Producer mode.
|
||||
* The logic here sets a producer and listener on CleverMicroRequest/CleverMicroResponse queues
|
||||
* created to enable the service discovery.
|
||||
* The Producer mode sends out routing data request on CleverMicroRequest and listens
|
||||
* on CleverMicroResponse for any service responding with its routing data.
|
||||
"""
|
||||
|
||||
self.channel = channel
|
||||
logging.info(" [*] RouterProducer.initRoutingPaths, channel open: %s", self.is_open(self.channel))
|
||||
if self.is_open(self.channel):
|
||||
try:
|
||||
# **** set up the service discovery internal exchanges ****
|
||||
# 1. declare RequestExchange to where to publish the RoutingData requests
|
||||
self.channel.exchange_declare(exchange=CM_REQUEST_EXCHANGE, exchange_type=ExchangeType.fanout)
|
||||
#
|
||||
# 2. declare ResponseExchange where to publish current RoutingData (response to
|
||||
# a RoutingData request)
|
||||
self.channel.exchange_declare(exchange=CM_RESPONSE_EXCHANGE, exchange_type=ExchangeType.fanout)
|
||||
# 2a. declare a Response queue for Routing Data replies from Services
|
||||
cm_response_queue: str = self.get_response_queue_name()
|
||||
self.channel.queue_declare(cm_response_queue, durable=True, exclusive=False, auto_delete=False,
|
||||
arguments={})
|
||||
# 2b. bind queue to CM_RESPONSE_EXCHANGE w/o any conditions (empty routing key)
|
||||
self.channel.queue_bind(queue=cm_response_queue, exchange=CM_RESPONSE_EXCHANGE, routing_key="#")
|
||||
# 2c. listen for incoming RoutingData messages
|
||||
self.channel.basic_consume(
|
||||
queue=cm_response_queue,
|
||||
auto_ack=False,
|
||||
on_message_callback=self.handle_routing_data_message)
|
||||
self.channel.start_consuming()
|
||||
logging.info(" [*] Routing master listens for Route updates on %s", cm_response_queue)
|
||||
except Exception as ioe:
|
||||
logging.error(" [E] RouterProducer FATAL: cannot initialize, %s", ioe)
|
||||
|
||||
else:
|
||||
logging.error(" [E] RouterProducer FATAL: cannot initialize, RabbitMQ channel is not open.")
|
||||
|
||||
def handle_routing_data_message(self,
|
||||
ch: BlockingChannel,
|
||||
method: spec.Basic.Deliver,
|
||||
properties: spec.BasicProperties,
|
||||
body: bytes):
|
||||
"""
|
||||
* Handler for CLEVER_MICRO_RESPONSE queue messages. Used in Master mode to handle receipt of
|
||||
* routing data, invokes addRoute or removeRoute depending on the type of the message.
|
||||
"""
|
||||
print(f" [x] Received {body}, m={method}, p={properties}, ch={ch}")
|
||||
"""
|
||||
logging.info(" [DBG] ******[%s] ROUTING_DATA %s >>> %s",
|
||||
delivery.getEnvelope().getDeliveryTag(),
|
||||
delivery.getEnvelope().toString(), str(delivery.getBody()))
|
||||
"""
|
||||
# ACK message now so that it is not being redelivered
|
||||
self.channel.basic_ack(method.delivery_tag, False)
|
||||
message: CleverMicroServiceMessage = self.service_message_factory.from_bytes(body)
|
||||
# ignore the message if it comes from ourselves (even if from different instance)
|
||||
logging.info(" [DBG] ******[%s] ROUTING_DATA, msg=%s, routeMap=[{] sn={, replyTo={",
|
||||
method.delivery_tag,
|
||||
self.service_message_factory.to_string(message),
|
||||
self.amq_configuration.amq_adapter.route_mapping,
|
||||
self.amq_configuration.amq_adapter.service_name,
|
||||
message.reply_to)
|
||||
if message.is_valid() and not self.amq_configuration.amq_adapter.service_name == message.reply_to:
|
||||
if message.message_type == ServiceMessageType.ROUTING_DATA:
|
||||
self.add_routes(message.message)
|
||||
if message.message_type == ServiceMessageType.ROUTING_DATA_REMOVE:
|
||||
self.remove_routes(message.message)
|
||||
|
||||
def request_routes_from_adapters(self):
|
||||
"""
|
||||
* Send a broadcast message prompting all listening adapters to reply with own routing data.
|
||||
"""
|
||||
logging.info(" [*] RouterProducer.requestRoutesFromAdapters, queue name: %s", CM_REQUEST_EXCHANGE)
|
||||
try:
|
||||
serviceMessage: CleverMicroServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA_REQUEST, "", ANY_RECIPIENT
|
||||
)
|
||||
if not self.publish_service_message(serviceMessage, CM_REQUEST_EXCHANGE):
|
||||
logging.warning(" [E] RouterProducer could not request route data: %s channel is not open.",
|
||||
CM_REQUEST_EXCHANGE)
|
||||
|
||||
except Exception as ioe:
|
||||
logging.error(" [E] RouterProducer failed request routing data: %s", ioe)
|
||||
|
||||
BASIC: BasicProperties = BasicProperties(content_type="application/octet-stream",
|
||||
content_encoding=None,
|
||||
headers=None,
|
||||
delivery_mode=1,
|
||||
priority=0,
|
||||
correlation_id=None)
|
||||
|
||||
def publish_service_message(self, message: CleverMicroServiceMessage, exchange_name: str) -> bool:
|
||||
"""
|
||||
* Publishes a service message to the service queue and logs success log entry.
|
||||
*
|
||||
* :param message: Service Message to be sent
|
||||
* :param exchangeName: target exchange name where to publish the message
|
||||
* :param queueName target: queue name (Optional, purpose is to get the queue size only)
|
||||
* :return True if channel is open, False otherwise
|
||||
* @throws IOException if something else goes wrong
|
||||
"""
|
||||
if self.is_open(self.channel):
|
||||
binary_content: bytes = self.service_message_factory.serialize(message)
|
||||
self.channel.basic_publish(exchange=exchange_name,
|
||||
routing_key="", # empty routing key
|
||||
properties=self.BASIC,
|
||||
body=binary_content)
|
||||
logging.info(" [x] Service Message Sent to %s, msg: %s", exchange_name, str(binary_content))
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_reply_to_queue_name(self) -> str:
|
||||
"""
|
||||
* self should create a unique name for self process and also when there's several instances.
|
||||
* @return unique reply-to queue name.
|
||||
"""
|
||||
return self.amq_configuration.amq_adapter.adapter_prefix() + CM_P_REPLY_TO
|
||||
|
||||
def get_response_queue_name(self) -> str:
|
||||
"""
|
||||
* self should create a unique name for self process and also when there's several instances.
|
||||
* @return unique reply-to queue name.
|
||||
"""
|
||||
return self.amq_configuration.amq_adapter.adapter_prefix() + CM_P_RESP_QUEUE
|
||||
|
||||
def get_consume_queue_name(self) -> str:
|
||||
"""
|
||||
* self should create a unique name for self process and also when there's several instances.
|
||||
* @return unique reply-to queue name.
|
||||
"""
|
||||
return self.amq_configuration.amq_adapter.adapter_prefix() + CM_C_AMQ_QUEUE
|
||||
|
||||
def is_open(self, _channel: pika.adapters.blocking_connection.BlockingChannel) -> bool:
|
||||
"""
|
||||
* Test if the channel is usable (opened).
|
||||
* :param _channel: the channel
|
||||
* :return True if the channel is opened.
|
||||
"""
|
||||
return _channel is not None and _channel.is_open
|
||||
@@ -0,0 +1,61 @@
|
||||
import struct
|
||||
from io import BytesIO
|
||||
from typing import List, Union, Dict
|
||||
|
||||
|
||||
def long_to_bytes(x: int) -> bytes:
|
||||
return struct.pack('q', x)
|
||||
|
||||
|
||||
def bytes_to_long(b: bytes) -> int:
|
||||
return struct.unpack('q', b)[0]
|
||||
|
||||
|
||||
def read_long(bis: BytesIO) -> int:
|
||||
try:
|
||||
return struct.unpack('q', bis.read(8))[0]
|
||||
except IOError:
|
||||
return 0
|
||||
|
||||
|
||||
def object_or_list_as_string(value: Union[str, List[str]]) -> str:
|
||||
if isinstance(value, list):
|
||||
return f"[{','.join(value)}]"
|
||||
return str(value)
|
||||
|
||||
|
||||
def map_as_string(map_: Dict[str, str]) -> str:
|
||||
return ','.join(f"{k}={object_or_list_as_string(v)}" for k, v in map_.items())
|
||||
|
||||
|
||||
def map_with_list_as_string(map_: Dict[str, List[str]]) -> str:
|
||||
return ','.join(f"{k}={object_or_list_as_string(v)}" for k, v in map_.items())
|
||||
|
||||
|
||||
def parse_map_string(input_str: str) -> Dict[str, str]:
|
||||
map_ = {}
|
||||
if input_str.startswith('{') and input_str.endswith('}'):
|
||||
for entry in input_str[1:-1].split(','):
|
||||
if len(entry) > 1:
|
||||
key, value = entry.split('=')
|
||||
map_[key] = value
|
||||
return map_
|
||||
|
||||
|
||||
def csv_as_list(input_str: str) -> List[str]:
|
||||
return [item.strip() for item in input_str.strip('[]').split(',')]
|
||||
|
||||
|
||||
def add_to_map(map_: Dict[str, List[str]], token: str) -> None:
|
||||
eq_idx = token.index('=')
|
||||
if eq_idx > 0:
|
||||
key = token[:eq_idx]
|
||||
map_[key] = csv_as_list(token[eq_idx+1:])
|
||||
|
||||
|
||||
def parse_map_list_string(input_str: str) -> Dict[str, List[str]]:
|
||||
map_ = {}
|
||||
if input_str.startswith('{') and input_str.endswith('}'):
|
||||
for token in input_str[1:-1].split('],'):
|
||||
add_to_map(map_, token)
|
||||
return map_
|
||||
@@ -0,0 +1,24 @@
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestRouteDatabase(TestCase):
|
||||
def test_get_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_pattern(self):
|
||||
self.fail()
|
||||
|
||||
def test_matches(self):
|
||||
self.fail()
|
||||
|
||||
def test_wrap_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_find_route(self):
|
||||
self.fail()
|
||||
|
||||
def test_add_route(self):
|
||||
self.fail()
|
||||
|
||||
def test_remove_route(self):
|
||||
self.fail()
|
||||
@@ -0,0 +1,39 @@
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestRouterBase(TestCase):
|
||||
def test_get_routing_key(self):
|
||||
self.fail()
|
||||
|
||||
def test_wrap_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_wrap_own_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_unwrap_route_list(self):
|
||||
self.fail()
|
||||
|
||||
def test_get_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_find_route(self):
|
||||
self.fail()
|
||||
|
||||
def test_find_route_by_message(self):
|
||||
self.fail()
|
||||
|
||||
def test_add_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_remove_routes(self):
|
||||
self.fail()
|
||||
|
||||
def test_remove_route(self):
|
||||
self.fail()
|
||||
|
||||
def test_add_route(self):
|
||||
self.fail()
|
||||
|
||||
def test_add_own_route(self):
|
||||
self.fail()
|
||||
@@ -0,0 +1,15 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from amqp.router.utils import sanitize_as_rabbitmq_name
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_sanitize_as_rabbitmq_name(self):
|
||||
self.assertEqual("aa.bb.cc", sanitize_as_rabbitmq_name("aa.bb/cc"))
|
||||
self.assertEqual("aa.bb.cc", sanitize_as_rabbitmq_name("aa-bb/cc"))
|
||||
self.assertEqual("aa", sanitize_as_rabbitmq_name("aa&bb/cc"))
|
||||
self.assertEqual("aa", sanitize_as_rabbitmq_name("aa+bb/cc"))
|
||||
self.assertEqual("aa", sanitize_as_rabbitmq_name("aa%bb/cc"))
|
||||
self.assertEqual("aa.bb.cc", sanitize_as_rabbitmq_name("aa#bb/cc"))
|
||||
self.assertEqual("aa", sanitize_as_rabbitmq_name("aa?bb////cc"))
|
||||
self.assertEqual("aa.bb.cc", sanitize_as_rabbitmq_name("aa///bb////cc"))
|
||||
@@ -0,0 +1,9 @@
|
||||
import re
|
||||
|
||||
|
||||
def sanitize_as_rabbitmq_name(input_str: str) -> str:
|
||||
pattern = re.compile(r"[^a-zA-Z0-9.#/\-]")
|
||||
matcher = pattern.search(input_str)
|
||||
token = input_str[:matcher.start()] if matcher else input_str
|
||||
step1 = re.sub(r"[^A-Za-z0-9]", ".", token)
|
||||
return re.sub(r"\.+", ".", step1)
|
||||
Reference in New Issue
Block a user