import logging from typing import Callable, List, Set from amqp.adapter.amq_route_factory import AMQRouteFactory, NULL_ROUTE from amqp.adapter.logging_utils import logging_error from amqp.model.model import AMQRoute, DataMessage 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: DataMessage) -> 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: DataMessage) -> 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("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)) logging.info( f" [*] RouterMaster.removeRoutes(): {message}, removed={removed_count}" ) except IOError as err: logging.info(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)