feat/#1 - applied 'black' formatting
This commit is contained in:
@@ -15,7 +15,7 @@ class RouteDatabase:
|
||||
|
||||
def pattern(self, routing_key: str) -> re.Pattern:
|
||||
breaker = False
|
||||
tokens = routing_key.split('.')
|
||||
tokens = routing_key.split(".")
|
||||
counter = len(tokens)
|
||||
regex_parts = []
|
||||
|
||||
@@ -40,9 +40,13 @@ class RouteDatabase:
|
||||
return re.compile(".*")
|
||||
return re.compile(regex)
|
||||
|
||||
def matches(self, routing_key_from_route: str, routing_key_from_message: str) -> bool:
|
||||
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)))
|
||||
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)
|
||||
|
||||
@@ -38,7 +38,10 @@ class RouterBase:
|
||||
|
||||
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(",")]
|
||||
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]:
|
||||
@@ -53,13 +56,19 @@ class RouterBase:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
@@ -73,9 +82,14 @@ class RouterBase:
|
||||
|
||||
def remove_routes(self, message: str) -> None:
|
||||
try:
|
||||
to_remove = [AMQRouteFactory.from_string(route_str) for route_str in message.split(",")]
|
||||
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))
|
||||
logging.info(f" [*] RouterMaster.removeRoutes(): {message}, removed={removed_count}")
|
||||
logging.info(
|
||||
f" [*] RouterMaster.removeRoutes(): {message}, removed={removed_count}"
|
||||
)
|
||||
except IOError as err:
|
||||
logging.info(f" [E] Can't remove route(s): {err}")
|
||||
|
||||
|
||||
+123
-51
@@ -1,27 +1,36 @@
|
||||
import logging
|
||||
from typing import Set
|
||||
|
||||
from aio_pika.abc import AbstractRobustChannel, AbstractRobustQueue, AbstractIncomingMessage
|
||||
from aio_pika.abc import (
|
||||
AbstractRobustChannel,
|
||||
AbstractRobustQueue,
|
||||
AbstractIncomingMessage,
|
||||
)
|
||||
from amqp.adapter.amq_route_factory import AMQRouteFactory
|
||||
from amqp.adapter.logging_utils import logging_error
|
||||
from amqp.config.amq_configuration import AMQConfiguration
|
||||
from amqp.model.model import ServiceMessage, AMQRoute
|
||||
from amqp.model.service_message_type import ServiceMessageType
|
||||
from amqp.router.router_base import CM_C_REQ_QUEUE, CM_REQUEST_EXCHANGE, CM_RESPONSE_EXCHANGE, ANY_RECIPIENT
|
||||
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.
|
||||
* 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):
|
||||
@@ -31,94 +40,148 @@ class RouterConsumer(RouterProducer):
|
||||
|
||||
async def init_routing_paths_consumer(self, _channel: AbstractRobustChannel):
|
||||
"""
|
||||
* Initialize router according to Consumer mode.
|
||||
* Initialize router according to Consumer mode.
|
||||
"""
|
||||
logging.info(" [*] RouterConsumer.initConsumerRouter, channel open: %s", self.is_open(_channel))
|
||||
logging.info(
|
||||
" [*] RouterConsumer.initConsumerRouter, channel open: %s",
|
||||
self.is_open(_channel),
|
||||
)
|
||||
if self.is_open(_channel):
|
||||
try:
|
||||
# 1. declare a Queue to receive the RoutingData requests
|
||||
_cm_request_queue: str = self.amq_configuration.amq_adapter.adapter_prefix() + CM_C_REQ_QUEUE
|
||||
_queue: AbstractRobustQueue = await self.channel.declare_queue(name=_cm_request_queue, durable=True, exclusive=False, auto_delete=False)
|
||||
_cm_request_queue: str = (
|
||||
self.amq_configuration.amq_adapter.adapter_prefix() + CM_C_REQ_QUEUE
|
||||
)
|
||||
_queue: AbstractRobustQueue = await self.channel.declare_queue(
|
||||
name=_cm_request_queue,
|
||||
durable=True,
|
||||
exclusive=False,
|
||||
auto_delete=False,
|
||||
)
|
||||
# 1a. bind queue to CM_REQUEST_EXCHANGE w/o any conditions (empty routing key)
|
||||
await _queue.bind(exchange=CM_REQUEST_EXCHANGE, routing_key="")
|
||||
# 1b. Service adapter mode - listens on Request queue and replies to Response queue
|
||||
_c_tag = await _queue.consume(callback=self.handle_routing_data_request_message, no_ack=False)
|
||||
logging.info(" [%s] RouterConsumer listens for RouteDataRequests on: %s", _c_tag, _cm_request_queue)
|
||||
_c_tag = await _queue.consume(
|
||||
callback=self.handle_routing_data_request_message, no_ack=False
|
||||
)
|
||||
logging.info(
|
||||
" [%s] RouterConsumer listens for RouteDataRequests on: %s",
|
||||
_c_tag,
|
||||
_cm_request_queue,
|
||||
)
|
||||
except Exception as ioe:
|
||||
logging_error("RouterConsumer FATAL: cannot initialize, %s", ioe)
|
||||
else:
|
||||
logging_error("RouterConsumer FATAL: cannot initialize, RabbitMQ channel is not open.")
|
||||
logging_error(
|
||||
"RouterConsumer FATAL: cannot initialize, RabbitMQ channel is not open."
|
||||
)
|
||||
|
||||
"""
|
||||
* ServiceMessageType.ROUTING_DATA_REQUEST handler listening on queue 'CleverMicroRequest'
|
||||
"""
|
||||
|
||||
async def handle_routing_data_request_message(self, message: AbstractIncomingMessage):
|
||||
async def handle_routing_data_request_message(
|
||||
self, message: AbstractIncomingMessage
|
||||
):
|
||||
# some debug statements, TODO - remove for prod release
|
||||
delivery_tag = message.delivery_tag
|
||||
debug = "Delivery.Properties = " + message.properties.__str__()
|
||||
logging.info(" [DBG] ******[%s] (ROUTING_DATA_REQ) tag: %s, env: %s, props: %s",
|
||||
delivery_tag, message.consumer_tag, message.properties, debug)
|
||||
logging.info(
|
||||
" [DBG] ******[%s] (ROUTING_DATA_REQ) tag: %s, env: %s, props: %s",
|
||||
delivery_tag,
|
||||
message.consumer_tag,
|
||||
message.properties,
|
||||
debug,
|
||||
)
|
||||
|
||||
await message.ack(False)
|
||||
|
||||
cm_message: ServiceMessage = self.service_message_factory.from_bytes(message.body)
|
||||
cm_message: ServiceMessage = self.service_message_factory.from_bytes(
|
||||
message.body
|
||||
)
|
||||
route_mapping: str = self.wrap_own_routes()
|
||||
logging.info(" [DBG] ******[%s] (ROUTING_DATA_REQ) msg=%s, routeMap=[%s]",
|
||||
delivery_tag, self.service_message_factory.to_string(cm_message), route_mapping)
|
||||
logging.info(
|
||||
" [DBG] ******[%s] (ROUTING_DATA_REQ) msg=%s, routeMap=[%s]",
|
||||
delivery_tag,
|
||||
self.service_message_factory.to_string(cm_message),
|
||||
route_mapping,
|
||||
)
|
||||
# Reply only if self adapter has own routeMapping (e.g. API-gateway-side adapter has none)
|
||||
if cm_message.is_valid() and cm_message.message_type == ServiceMessageType.ROUTING_DATA_REQUEST and route_mapping:
|
||||
if (
|
||||
cm_message.is_valid()
|
||||
and cm_message.message_type == ServiceMessageType.ROUTING_DATA_REQUEST
|
||||
and route_mapping
|
||||
):
|
||||
if cm_message.reply_to != self.amq_configuration.amq_adapter.service_name:
|
||||
try:
|
||||
service_message: ServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA, route_mapping, cm_message.reply_to
|
||||
ServiceMessageType.ROUTING_DATA,
|
||||
route_mapping,
|
||||
cm_message.reply_to,
|
||||
)
|
||||
await self.publish_service_message(
|
||||
service_message, self.cm_response_exchange
|
||||
)
|
||||
await self.publish_service_message(service_message, self.cm_response_exchange)
|
||||
except Exception as err:
|
||||
logging_error("******[%s] (ROUTING_DATA_REQ) Response to queue: %s failed: %s",
|
||||
delivery_tag, CM_RESPONSE_EXCHANGE, err)
|
||||
logging_error(
|
||||
"******[%s] (ROUTING_DATA_REQ) Response to queue: %s failed: %s",
|
||||
delivery_tag,
|
||||
CM_RESPONSE_EXCHANGE,
|
||||
err,
|
||||
)
|
||||
else:
|
||||
logging.info(" [*] ******[%s] (ROUTING_DATA_REQ) [%s] Ignore request originated by ourselves.")
|
||||
logging.info(
|
||||
" [*] ******[%s] (ROUTING_DATA_REQ) [%s] Ignore request originated by ourselves."
|
||||
)
|
||||
else:
|
||||
logging_error("******[%s] (ROUTING_DATA_REQ) [%s] produces invalid or no CM msg type.",
|
||||
delivery_tag, str(message.body))
|
||||
logging_error(
|
||||
"******[%s] (ROUTING_DATA_REQ) [%s] produces invalid or no CM msg type.",
|
||||
delivery_tag,
|
||||
str(message.body),
|
||||
)
|
||||
|
||||
async 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
|
||||
* 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: ServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA, route.as_string(), ANY_RECIPIENT
|
||||
)
|
||||
await self.publish_service_message(serviceMessage, self.cm_response_exchange)
|
||||
await self.publish_service_message(
|
||||
serviceMessage, self.cm_response_exchange
|
||||
)
|
||||
self.add_own_route(route)
|
||||
logging.info(" [DBG] RouterConsumer registered Route: %s", route)
|
||||
else:
|
||||
logging.warning(" [W] RouterConsumer couldn't register route data: %s channel isn't open.",
|
||||
CM_RESPONSE_EXCHANGE)
|
||||
logging.warning(
|
||||
" [W] RouterConsumer couldn't register route data: %s channel isn't open.",
|
||||
CM_RESPONSE_EXCHANGE,
|
||||
)
|
||||
except Exception as ioe:
|
||||
logging_error("RouterConsumer failed register route data: %s", ioe)
|
||||
|
||||
async 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
|
||||
* Unregister / remove route from the list
|
||||
* :param route: route to remove
|
||||
* :return result of remove operation
|
||||
"""
|
||||
logging.info(" [DBG] RouterConsumer.removeRoute, route: %s", route.as_string())
|
||||
try:
|
||||
service_message: ServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA_REMOVE,
|
||||
route.as_string(),
|
||||
ANY_RECIPIENT
|
||||
ServiceMessageType.ROUTING_DATA_REMOVE, route.as_string(), ANY_RECIPIENT
|
||||
)
|
||||
if not await self.publish_service_message(service_message, self.cm_response_exchange):
|
||||
logging.warning(" [W] RouterConsumer couldn't remove current route: %s, channel not open.",
|
||||
route)
|
||||
if not await self.publish_service_message(
|
||||
service_message, self.cm_response_exchange
|
||||
):
|
||||
logging.warning(
|
||||
" [W] RouterConsumer couldn't remove current route: %s, channel not open.",
|
||||
route,
|
||||
)
|
||||
|
||||
except Exception as ioe:
|
||||
logging_error("RouterConsumer failed remove current route: %s", ioe)
|
||||
@@ -128,10 +191,19 @@ class RouterConsumer(RouterProducer):
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
* Cleanup - de-register routes with master.
|
||||
* 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)
|
||||
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)
|
||||
logging.info(
|
||||
" [W] %d route(s) not removed. self may lead to failed delivery on self route",
|
||||
not_removed,
|
||||
)
|
||||
|
||||
+133
-76
@@ -1,16 +1,22 @@
|
||||
"""
|
||||
* 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.
|
||||
"""
|
||||
* 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
|
||||
|
||||
from aio_pika import Message
|
||||
from aio_pika.abc import AbstractRobustChannel, AbstractRobustQueue, AbstractRobustConnection, AbstractRobustExchange, \
|
||||
AbstractIncomingMessage
|
||||
from aio_pika.abc import (
|
||||
AbstractRobustChannel,
|
||||
AbstractRobustQueue,
|
||||
AbstractRobustConnection,
|
||||
AbstractRobustExchange,
|
||||
AbstractIncomingMessage,
|
||||
)
|
||||
from pika.exchange_type import ExchangeType
|
||||
|
||||
from amqp.adapter.logging_utils import logging_error
|
||||
@@ -18,40 +24,54 @@ from amqp.adapter.service_message_factory import ServiceMessageFactory
|
||||
from amqp.config.amq_configuration import AMQConfiguration
|
||||
from amqp.model.model import ServiceMessage
|
||||
from amqp.model.service_message_type import 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
|
||||
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.
|
||||
"""
|
||||
* Initialize router - need to supply the config values and RabbitMQ channel.
|
||||
* :param configuration: adapter's configuration reference.
|
||||
"""
|
||||
super().__init__()
|
||||
self.connection: AbstractRobustConnection | None = None
|
||||
self.service_message_factory = ServiceMessageFactory(configuration.amq_adapter.service_name)
|
||||
self.service_message_factory = ServiceMessageFactory(
|
||||
configuration.amq_adapter.service_name
|
||||
)
|
||||
self.amq_configuration: AMQConfiguration = configuration
|
||||
self.channel: AbstractRobustChannel | None = None
|
||||
self.cm_request_exchange: AbstractRobustExchange | None = None
|
||||
self.cm_response_exchange: AbstractRobustExchange | None = None
|
||||
|
||||
logging.info(f" [*] RouterProducer.<init>: %s, route: %s",
|
||||
self.amq_configuration.amq_adapter.service_name,
|
||||
self.amq_configuration.amq_adapter.route_mapping)
|
||||
logging.info(
|
||||
f" [*] RouterProducer.<init>: %s, route: %s",
|
||||
self.amq_configuration.amq_adapter.service_name,
|
||||
self.amq_configuration.amq_adapter.route_mapping,
|
||||
)
|
||||
|
||||
async def init_internal_routing_paths(self, channel: AbstractRobustChannel | None):
|
||||
"""
|
||||
* Initialize internal routes for ServiceMessages.
|
||||
* 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.
|
||||
"""
|
||||
* Initialize internal routes for ServiceMessages.
|
||||
* 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))
|
||||
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 ****
|
||||
@@ -68,42 +88,68 @@ class RouterProducer(RouterBase):
|
||||
# 2a. declare a Response queue for Routing Data replies from Services
|
||||
_cm_response_queue: str = self.get_response_queue_name()
|
||||
_response_queue: AbstractRobustQueue = await self.channel.declare_queue(
|
||||
name=_cm_response_queue, durable=True, exclusive=False, auto_delete=False,
|
||||
arguments={}
|
||||
name=_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)
|
||||
await _response_queue.bind(exchange=CM_RESPONSE_EXCHANGE, routing_key="#")
|
||||
await _response_queue.bind(
|
||||
exchange=CM_RESPONSE_EXCHANGE, routing_key="#"
|
||||
)
|
||||
# 2c. listen for incoming RoutingData messages
|
||||
_c_tag = await _response_queue.consume(callback=self.handle_routing_data_message, no_ack=False)
|
||||
logging.info(" [%s] Routing master listens for Route updates on %s", _c_tag, _cm_response_queue)
|
||||
_c_tag = await _response_queue.consume(
|
||||
callback=self.handle_routing_data_message, no_ack=False
|
||||
)
|
||||
logging.info(
|
||||
" [%s] Routing master listens for Route updates on %s",
|
||||
_c_tag,
|
||||
_cm_response_queue,
|
||||
)
|
||||
except Exception as ioe:
|
||||
logging_error("RouterProducer FATAL: cannot initialize, %s", ioe)
|
||||
|
||||
else:
|
||||
logging_error("RouterProducer FATAL: cannot initialize, RabbitMQ channel is not open.")
|
||||
logging_error(
|
||||
"RouterProducer FATAL: cannot initialize, RabbitMQ channel is not open."
|
||||
)
|
||||
|
||||
async def handle_routing_data_message(self, message: AbstractIncomingMessage):
|
||||
"""
|
||||
* 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.
|
||||
"""
|
||||
logging.info(f" [x] Received {message.body}, m={message.message_id}, p={message.properties}")
|
||||
* 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.
|
||||
"""
|
||||
logging.info(
|
||||
f" [x] Received {message.body}, m={message.message_id}, p={message.properties}"
|
||||
)
|
||||
"""
|
||||
logging.info(" [DBG] ******[%s] ROUTING_DATA %s >>> %s",
|
||||
delivery.getEnvelope().getDeliveryTag(),
|
||||
delivery.getEnvelope().toString(), str(delivery.getBody()))
|
||||
"""
|
||||
logging.info(" [%s] ACK message now so that it is not being redelivered", message.delivery_tag)
|
||||
logging.info(
|
||||
" [%s] ACK message now so that it is not being redelivered",
|
||||
message.delivery_tag,
|
||||
)
|
||||
await message.ack(multiple=False)
|
||||
cm_message: ServiceMessage = self.service_message_factory.from_bytes(message.body)
|
||||
cm_message: ServiceMessage = self.service_message_factory.from_bytes(
|
||||
message.body
|
||||
)
|
||||
# ignore the message if it comes from ourselves (even if from different instance)
|
||||
logging.info(" [DBG] ******[%s] ROUTING_DATA, msg=%s, routeMap=[%s] sn=%s, replyTo=%s",
|
||||
message.delivery_tag,
|
||||
self.service_message_factory.to_string(cm_message),
|
||||
self.amq_configuration.amq_adapter.route_mapping,
|
||||
self.amq_configuration.amq_adapter.service_name,
|
||||
cm_message.reply_to)
|
||||
if cm_message.is_valid() and not self.amq_configuration.amq_adapter.service_name == cm_message.reply_to:
|
||||
logging.info(
|
||||
" [DBG] ******[%s] ROUTING_DATA, msg=%s, routeMap=[%s] sn=%s, replyTo=%s",
|
||||
message.delivery_tag,
|
||||
self.service_message_factory.to_string(cm_message),
|
||||
self.amq_configuration.amq_adapter.route_mapping,
|
||||
self.amq_configuration.amq_adapter.service_name,
|
||||
cm_message.reply_to,
|
||||
)
|
||||
if (
|
||||
cm_message.is_valid()
|
||||
and not self.amq_configuration.amq_adapter.service_name
|
||||
== cm_message.reply_to
|
||||
):
|
||||
if cm_message.message_type == ServiceMessageType.ROUTING_DATA:
|
||||
self.add_routes(cm_message.message)
|
||||
if cm_message.message_type == ServiceMessageType.ROUTING_DATA_REMOVE:
|
||||
@@ -111,72 +157,83 @@ class RouterProducer(RouterBase):
|
||||
|
||||
async 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)
|
||||
* 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: ServiceMessage = self.service_message_factory.of(
|
||||
ServiceMessageType.ROUTING_DATA_REQUEST, "", ANY_RECIPIENT
|
||||
)
|
||||
if not await self.publish_service_message(serviceMessage, self.cm_request_exchange):
|
||||
logging.warning(" [E] RouterProducer could not request route data: %s channel is not open.",
|
||||
CM_REQUEST_EXCHANGE)
|
||||
if not await self.publish_service_message(
|
||||
serviceMessage, self.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("RouterProducer failed request routing data: %s", ioe)
|
||||
|
||||
async def publish_service_message(self,
|
||||
message: ServiceMessage,
|
||||
exchange: AbstractRobustExchange) -> bool:
|
||||
async def publish_service_message(
|
||||
self, message: ServiceMessage, exchange: AbstractRobustExchange
|
||||
) -> bool:
|
||||
"""
|
||||
* Publishes a service message to the service queue and logs success log entry.
|
||||
*
|
||||
* :param message: Service Message to be sent
|
||||
* :param exchange: target exchange name where to publish the message
|
||||
* :return True if channel is open, False otherwise
|
||||
* @throws IOException if something else goes wrong
|
||||
"""
|
||||
* Publishes a service message to the service queue and logs success log entry.
|
||||
*
|
||||
* :param message: Service Message to be sent
|
||||
* :param exchange: target exchange name where to publish the message
|
||||
* :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)
|
||||
pika_message: Message = Message(
|
||||
body=binary_content,
|
||||
content_encoding='utf-8',
|
||||
content_encoding="utf-8",
|
||||
delivery_mode=2,
|
||||
content_type="application/octet-stream",
|
||||
headers=None,
|
||||
priority=0,
|
||||
correlation_id=None
|
||||
correlation_id=None,
|
||||
)
|
||||
await exchange.publish(message=pika_message, routing_key="")
|
||||
logging.info(" [x] Service Message Sent to %s, msg: %s", exchange.name, str(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.
|
||||
"""
|
||||
* 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.
|
||||
"""
|
||||
* 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.
|
||||
"""
|
||||
* 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: AbstractRobustChannel) -> bool:
|
||||
"""
|
||||
* Test if the channel is usable (opened).
|
||||
* :param _channel: the channel
|
||||
* :return True if the channel is opened.
|
||||
"""
|
||||
* Test if the channel is usable (opened).
|
||||
* :param _channel: the channel
|
||||
* :return True if the channel is opened.
|
||||
"""
|
||||
return _channel is not None and not _channel.is_closed
|
||||
|
||||
@@ -28,7 +28,9 @@ class TestRouteDatabase(TestCase):
|
||||
|
||||
def test_get_routes(self):
|
||||
_amq_factory = AMQRouteFactory()
|
||||
_route: AMQRoute = _amq_factory.from_string("amq-clever-pybook::clever-pybook:clever.pybook.localhost.#:5:0")
|
||||
_route: AMQRoute = _amq_factory.from_string(
|
||||
"amq-clever-pybook::clever-pybook:clever.pybook.localhost.#:5:0"
|
||||
)
|
||||
_routes = self._database.get_routes()
|
||||
self.assertIsNotNone(_routes)
|
||||
self.assertEqual(3, len(_routes))
|
||||
@@ -76,8 +78,7 @@ class TestRouteDatabase(TestCase):
|
||||
|
||||
def test_find_route(self):
|
||||
message = DataMessageFactory.get_instance(111).create_request_message(
|
||||
"POST", "clever3-book.localhost",
|
||||
"/aa/bb?cc=d", {}, ""
|
||||
"POST", "clever3-book.localhost", "/aa/bb?cc=d", {}, ""
|
||||
)
|
||||
route = self._database.find_route(message.domain, message.path)
|
||||
self.assertIsNotNone(route)
|
||||
|
||||
@@ -44,7 +44,9 @@ class TestRouterBase(TestCase):
|
||||
self.assertEqual("aa.bb.cc", sanitize_as_rabbitmq_name("aa///bb////cc"))
|
||||
|
||||
def test_get_routing_key(self):
|
||||
message = self.factory.create_request_message("POST", "test.me.localhost", "/aa/bb?cc=d", {}, "")
|
||||
message = self.factory.create_request_message(
|
||||
"POST", "test.me.localhost", "/aa/bb?cc=d", {}, ""
|
||||
)
|
||||
rk = self.base.get_routing_key(message)
|
||||
self.assertEqual("test.me.localhost.aa.bb", rk)
|
||||
|
||||
@@ -80,8 +82,7 @@ class TestRouterBase(TestCase):
|
||||
|
||||
def test_find_route_by_message(self):
|
||||
message = DataMessageFactory.get_instance(111).create_request_message(
|
||||
"POST", "clever3-book.localhost",
|
||||
"/aa/bb?cc=d", {}, ""
|
||||
"POST", "clever3-book.localhost", "/aa/bb?cc=d", {}, ""
|
||||
)
|
||||
_route: AMQRoute = self.base.find_route_by_message(message)
|
||||
self.assertIsNotNone(_route)
|
||||
@@ -91,7 +92,7 @@ class TestRouterBase(TestCase):
|
||||
init_routes: Set[AMQRoute] = self.base.get_routes().copy()
|
||||
self.base.add_routes(ROUTE_6 + ",amq-dlq:DLX:DeadLetterQueue:#:0:0,:::::,:::")
|
||||
modified = self.base.get_routes()
|
||||
self.assertEqual(len(init_routes)+1, len(modified))
|
||||
self.assertEqual(len(init_routes) + 1, len(modified))
|
||||
wr = self.base.wrap_routes()
|
||||
self.assertFalse("DLX" in wr)
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ pattern = re.compile(r"[^a-zA-Z0-9.#/\-]")
|
||||
|
||||
def sanitize_as_rabbitmq_name(input_str: str) -> str:
|
||||
matcher = pattern.search(input_str)
|
||||
token = input_str[:matcher.start()] if matcher else input_str
|
||||
token = input_str[: matcher.start()] if matcher else input_str
|
||||
return sanitize_as_rabbitmq_domain_name(token)
|
||||
|
||||
|
||||
def sanitize_as_rabbitmq_domain_name(token: str) -> str:
|
||||
# Step 1: Replace non-alphanumeric characters (except periods) with a period
|
||||
step1 = re.sub(r'[^A-Za-z0-9.#]', '.', token)
|
||||
step1 = re.sub(r"[^A-Za-z0-9.#]", ".", token)
|
||||
# Step 2: Replace multiple consecutive periods with a single period
|
||||
step2 = re.sub(r'\.+', '.', step1)
|
||||
step2 = re.sub(r"\.+", ".", step1)
|
||||
# Step 3: Remove trailing '.' character, if applicable
|
||||
step_last_char = len(step2) - 1
|
||||
return step2[:-1] if step_last_char > 0 and step2[step_last_char] == '.' else step2
|
||||
return step2[:-1] if step_last_char > 0 and step2[step_last_char] == "." else step2
|
||||
|
||||
Reference in New Issue
Block a user