class ConfigsManager(PublisherSubscriberComponent): """ This class reads all configurations and sends them over to the "config" topic in Rabbit MQ. Updated configs are sent as well """ def __init__(self, name: str, logger: logging.Logger, config_directory: str, rabbit_ip: str, file_patterns: Optional[List[str]] = None, ignore_file_patterns: Optional[List[str]] = None, ignore_directories: bool = True, case_sensitive: bool = False): """ Constructs the ConfigsManager instance :param config_directory: The root config directory to watch. This is searched recursively. :param file_patterns: The file patterns in the directory to watch. Defaults to all ini files :param ignore_file_patterns: Any file patterns to ignore. Defaults to None :param ignore_directories: Whether changes in directories should be ignored. Default: True :param case_sensitive: Whether the patterns in `file_patterns` and `ignore_file_patterns` are case sensitive. Defaults to False """ if not file_patterns: file_patterns = ['*.ini'] self._name = name self._config_directory = config_directory self._file_patterns = file_patterns self._watching = False self._connected_to_rabbit = False logger.debug("Creating config RabbitMQ connection") rabbitmq = RabbitMQApi(logger.getChild("config_{}".format( RabbitMQApi.__name__)), host=rabbit_ip) super().__init__(logger, rabbitmq) self._logger.debug("Creating heartbeat RabbitMQ connection") self._heartbeat_rabbit = RabbitMQApi(logger.getChild( "heartbeat_{}".format(RabbitMQApi.__name__)), host=rabbit_ip) self._event_handler = ConfigFileEventHandler( self._logger.getChild(ConfigFileEventHandler.__name__), self._on_event_thrown, file_patterns, ignore_file_patterns, ignore_directories, case_sensitive) self._observer = PollingObserver() self._observer.schedule(self._event_handler, config_directory, recursive=True) def __str__(self) -> str: return self.name @property def name(self) -> str: return self._name def _initialise_rabbitmq(self) -> None: while True: try: self._connect_to_rabbit() self._logger.info("Connected to Rabbit") self.rabbitmq.confirm_delivery() self._logger.info("Enabled delivery confirmation on configs" "RabbitMQ channel") self.rabbitmq.exchange_declare(CONFIG_EXCHANGE, 'topic', False, True, False, False) self._logger.info("Declared %s exchange in Rabbit", CONFIG_EXCHANGE) self._heartbeat_rabbit.confirm_delivery() self._logger.info("Enabled delivery confirmation on heartbeat" "RabbitMQ channel") self._heartbeat_rabbit.exchange_declare( HEALTH_CHECK_EXCHANGE, 'topic', False, True, False, False) self._logger.info("Declared %s exchange in Rabbit", HEALTH_CHECK_EXCHANGE) self._logger.info( "Creating and binding queue '%s' to exchange '%s' with " "routing key '%s", CONFIG_PING_QUEUE, HEALTH_CHECK_EXCHANGE, _HEARTBEAT_ROUTING_KEY) self._heartbeat_rabbit.queue_declare(CONFIG_PING_QUEUE, False, True, False, False) self._logger.debug("Declared '%s' queue", CONFIG_PING_QUEUE) self._heartbeat_rabbit.queue_bind(CONFIG_PING_QUEUE, HEALTH_CHECK_EXCHANGE, 'ping') self._logger.debug("Bound queue '%s' to exchange '%s'", CONFIG_PING_QUEUE, HEALTH_CHECK_EXCHANGE) # Pre-fetch count is set to 300 prefetch_count = round(300) self._heartbeat_rabbit.basic_qos(prefetch_count=prefetch_count) self._logger.debug("Declaring consuming intentions") self._heartbeat_rabbit.basic_consume(CONFIG_PING_QUEUE, self._process_ping, True, False, None) break except (ConnectionNotInitialisedException, AMQPConnectionError) as connection_error: # Should be impossible, but since exchange_declare can throw # it we shall ensure to log that the error passed through here # too. self._logger.error( "Something went wrong that meant a connection was not made" ) self._logger.error(connection_error.message) raise connection_error except AMQPChannelError: # This error would have already been logged by the RabbitMQ # logger and handled by RabbitMQ. As a result we don't need to # anything here, just re-try. time.sleep(RE_INITIALISE_SLEEPING_PERIOD) def _connect_to_rabbit(self) -> None: if not self._connected_to_rabbit: self._logger.info("Connecting to the config RabbitMQ") self.rabbitmq.connect_till_successful() self._logger.info("Connected to config RabbitMQ") self._logger.info("Connecting to the heartbeat RabbitMQ") self._heartbeat_rabbit.connect_till_successful() self._logger.info("Connected to heartbeat RabbitMQ") self._connected_to_rabbit = True else: self._logger.info( "Already connected to RabbitMQ, will not connect again") def disconnect_from_rabbit(self) -> None: if self._connected_to_rabbit: self._logger.info("Disconnecting from the config RabbitMQ") self.rabbitmq.disconnect_till_successful() self._logger.info("Disconnected from the config RabbitMQ") self._logger.info("Disconnecting from the heartbeat RabbitMQ") self._heartbeat_rabbit.disconnect_till_successful() self._logger.info("Disconnected from the heartbeat RabbitMQ") self._connected_to_rabbit = False else: self._logger.info("Already disconnected from RabbitMQ") def _send_heartbeat(self, data_to_send: dict) -> None: self._logger.debug("Sending heartbeat to the %s exchange", HEALTH_CHECK_EXCHANGE) self._logger.debug("Sending %s", data_to_send) self._heartbeat_rabbit.basic_publish_confirm( exchange=HEALTH_CHECK_EXCHANGE, routing_key=_HEARTBEAT_ROUTING_KEY, body=data_to_send, is_body_dict=True, properties=pika.BasicProperties(delivery_mode=2), mandatory=True) self._logger.debug("Sent heartbeat to %s exchange", HEALTH_CHECK_EXCHANGE) def _process_ping(self, ch: BlockingChannel, method: pika.spec.Basic.Deliver, properties: pika.spec.BasicProperties, body: bytes) -> None: self._logger.debug("Received %s. Let's pong", body) try: heartbeat = { 'component_name': self.name, 'is_alive': self._observer.is_alive(), 'timestamp': datetime.now().timestamp(), } self._send_heartbeat(heartbeat) except MessageWasNotDeliveredException as e: # Log the message and do not raise it as heartbeats must be # real-time self._logger.error("Error when sending heartbeat") self._logger.exception(e) def _send_data(self, config: Dict[str, Any], route_key: str) -> None: self._logger.debug("Sending %s with routing key %s", config, route_key) while True: try: self._logger.debug( "Attempting to send config with routing key %s", route_key) # We need to definitely send this self.rabbitmq.basic_publish_confirm( CONFIG_EXCHANGE, route_key, config, mandatory=True, is_body_dict=True, properties=BasicProperties(delivery_mode=2)) self._logger.info("Configuration update sent") break except MessageWasNotDeliveredException as mwnde: self._logger.error( "Config was not successfully sent with " "routing key %s", route_key) self._logger.exception(mwnde) self._logger.info( "Will attempt sending the config again with " "routing key %s", route_key) self.rabbitmq.connection.sleep(10) except (ConnectionNotInitialisedException, AMQPConnectionError) as connection_error: # If the connection is not initialised or there is a connection # error, we need to restart the connection and try it again self._logger.error("There has been a connection error") self._logger.exception(connection_error) self._logger.info("Restarting the connection") self._connected_to_rabbit = False # Wait some time before reconnecting and then retrying time.sleep(RE_INITIALISE_SLEEPING_PERIOD) self._connect_to_rabbit() self._logger.info( "Connection restored, will attempt sending " "the config with routing key %s", route_key) except AMQPChannelError: # This error would have already been logged by the RabbitMQ # logger and handled by RabbitMQ. Since a new channel is created # we need to re-initialise RabbitMQ self._initialise_rabbitmq() def _on_event_thrown(self, event: FileSystemEvent) -> None: """ When an event is thrown, it reads the config and sends it as a dict via rabbitmq to the config exchange of type topic with the routing key determined by the relative file path. :param event: The event passed by watchdog :return None """ self._logger.debug("Event thrown: %s", event) self._logger.info("Detected a config %s in %s", event.event_type, event.src_path) if event.event_type == "deleted": self._logger.debug("Creating empty dict") config_dict = {} else: config = ConfigParser() self._logger.debug("Reading configuration") try: config.read(event.src_path) except (DuplicateSectionError, DuplicateOptionError, InterpolationError, ParsingError) as e: self._logger.error(e.message) # When the config is invalid, we do nothing and discard this # event. return None self._logger.debug("Config read successfully") config_dict = {key: dict(config[key]) for key in config} self._logger.debug("Config converted to dict: %s", config_dict) # Since the watcher is configured to watch files in # self._config_directory we only need check that (for get_routing_key) config_folder = os.path.normpath(self._config_directory) key = routing_key.get_routing_key(event.src_path, config_folder) self._logger.debug("Sending config %s to RabbitMQ with routing key %s", config_dict, key) self._send_data(config_dict, key) @property def config_directory(self) -> str: return self._config_directory @property def watching(self) -> bool: return self._watching @property def connected_to_rabbit(self) -> bool: return self._connected_to_rabbit def start(self) -> None: """ This method is used to start rabbit and the observer and begin watching the config files. It also sends the configuration files for the first time :return None """ log_and_print("{} started.".format(self), self._logger) self._initialise_rabbitmq() def do_first_run_event(name: str) -> None: event = FileSystemEvent(name) event.event_type = _FIRST_RUN_EVENT self._on_event_thrown(event) self._logger.info("Throwing first run event for all config files") self.foreach_config_file(do_first_run_event) if not self._watching: self._logger.info("Starting config file observer") self._observer.start() self._watching = True else: self._logger.info("File observer is already running") self._logger.debug("Config file observer started") self._connect_to_rabbit() self._listen_for_data() def _listen_for_data(self) -> None: self._logger.info("Starting the config ping listener") self._heartbeat_rabbit.start_consuming() def _on_terminate(self, signum: int, stack: FrameType) -> None: """ This method is used to stop the observer and join the threads """ log_and_print( "{} is terminating. Connections with RabbitMQ will be " "closed, and afterwards the process will exit.".format(self), self._logger) if self._watching: self._logger.info("Stopping config file observer") self._observer.stop() self._observer.join() self._watching = False self._logger.debug("Config file observer stopped") else: self._logger.info("Config file observer already stopped") self.disconnect_from_rabbit() log_and_print("{} terminated.".format(self), self._logger) sys.exit() def foreach_config_file(self, callback: Callable[[str], None]) -> None: """ Runs a function over all the files being watched by this class :param callback: The function to watch. Must accept a string for the file path as {config_directory} + {file path} :return: Nothing """ for root, dirs, files in os.walk(self.config_directory): for name in files: if any([ fnmatch.fnmatch(name, pattern) for pattern in self._file_patterns ]): callback(os.path.join(root, name))
class TestGithubAlertersManager(unittest.TestCase): def setUp(self) -> None: self.dummy_logger = logging.getLogger('Dummy') self.dummy_logger.disabled = True self.connection_check_time_interval = timedelta(seconds=0) self.rabbitmq = RabbitMQApi( self.dummy_logger, env.RABBIT_IP, connection_check_time_interval=self.connection_check_time_interval) self.manager_name = 'test_github_alerters_manager' self.test_queue_name = 'Test Queue' self.test_data_str = 'test data' self.test_heartbeat = { 'component_name': self.manager_name, 'is_alive': True, 'timestamp': datetime(2012, 1, 1).timestamp(), } self.github_alerter_name = GITHUB_ALERTER_NAME self.dummy_process1 = Process(target=infinite_fn, args=()) self.dummy_process1.daemon = True self.dummy_process2 = Process(target=infinite_fn, args=()) self.dummy_process2.daemon = True self.dummy_process3 = Process(target=infinite_fn, args=()) self.dummy_process3.daemon = True self.test_rabbit_manager = RabbitMQApi( self.dummy_logger, env.RABBIT_IP, connection_check_time_interval=self.connection_check_time_interval) self.test_manager = GithubAlerterManager( self.dummy_logger, self.manager_name, self.rabbitmq) self.test_exception = PANICException('test_exception', 1) def tearDown(self) -> None: # Delete any queues and exchanges which are common across many tests try: self.test_rabbit_manager.connect() self.test_manager.rabbitmq.connect() # Declare queues incase they haven't been declared already self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=False ) self.test_manager.rabbitmq.queue_declare( queue=GITHUB_MANAGER_INPUT_QUEUE, durable=True, exclusive=False, auto_delete=False, passive=False ) self.test_manager.rabbitmq.queue_purge(self.test_queue_name) self.test_manager.rabbitmq.queue_purge(GITHUB_MANAGER_INPUT_QUEUE) self.test_manager.rabbitmq.queue_delete(self.test_queue_name) self.test_manager.rabbitmq.queue_delete(GITHUB_MANAGER_INPUT_QUEUE) self.test_manager.rabbitmq.exchange_delete(HEALTH_CHECK_EXCHANGE) self.test_manager.rabbitmq.disconnect() self.test_rabbit_manager.disconnect() except Exception as e: print("Test failed: {}".format(e)) self.dummy_logger = None self.rabbitmq = None self.test_manager = None self.test_exception = None self.test_rabbit_manager = None self.dummy_process1 = None self.dummy_process2 = None self.dummy_process3 = None def test_str_returns_manager_name(self) -> None: self.assertEqual(self.manager_name, self.test_manager.__str__()) def test_name_returns_manager_name(self) -> None: self.assertEqual(self.manager_name, self.test_manager.name) @mock.patch.object(RabbitMQApi, "start_consuming") def test_listen_for_data_calls_start_consuming( self, mock_start_consuming) -> None: mock_start_consuming.return_value = None self.test_manager._listen_for_data() self.assertEqual(1, mock_start_consuming.call_count) @mock.patch.object(GithubAlerterManager, "_process_ping") def test_initialise_rabbitmq_initialises_everything_as_expected( self, mock_process_ping) -> None: mock_process_ping.return_value = None try: self.test_rabbit_manager.connect() # To make sure that there is no connection/channel already # established self.assertIsNone(self.rabbitmq.connection) self.assertIsNone(self.rabbitmq.channel) # To make sure that the exchanges and queues have not already been # declared self.test_manager.rabbitmq.connect() self.test_manager.rabbitmq.queue_delete(GITHUB_MANAGER_INPUT_QUEUE) self.test_manager.rabbitmq.exchange_delete(HEALTH_CHECK_EXCHANGE) self.test_manager._initialise_rabbitmq() # Perform checks that the connection has been opened, marked as open # and that the delivery confirmation variable is set. self.assertTrue(self.test_manager.rabbitmq.is_connected) self.assertTrue(self.test_manager.rabbitmq.connection.is_open) self.assertTrue( self.test_manager.rabbitmq.channel._delivery_confirmation) # Check whether the exchanges and queues have been creating by # sending messages with the same routing keys as for the queues. We # will also check if the size of the queues is 0 to confirm that # basic_consume was called (it will store the msg in the component # memory immediately). If one of the exchanges or queues is not # created, then either an exception will be thrown or the queue size # would be 1. Note when deleting the exchanges in the beginning we # also released every binding, hence there are no other queue binded # with the same routing key to any exchange at this point. self.test_rabbit_manager.basic_publish_confirm( exchange=HEALTH_CHECK_EXCHANGE, routing_key=GITHUB_MANAGER_INPUT_ROUTING_KEY, body=self.test_data_str, is_body_dict=False, properties=pika.BasicProperties(delivery_mode=2), mandatory=True) # Re-declare queue to get the number of messages res = self.test_rabbit_manager.queue_declare( GITHUB_MANAGER_INPUT_QUEUE, False, True, False, False) self.assertEqual(0, res.method.message_count) except Exception as e: self.fail("Test failed: {}".format(e)) def test_send_heartbeat_sends_a_heartbeat_correctly(self) -> None: # This test creates a queue which receives messages with the same # routing key as the ones sent by send_heartbeat, and checks that the # heartbeat is received try: self.test_manager._initialise_rabbitmq() self.test_rabbit_manager.connect() res = self.test_rabbit_manager.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=False ) self.assertEqual(0, res.method.message_count) self.test_manager.rabbitmq.queue_bind( queue=self.test_queue_name, exchange=HEALTH_CHECK_EXCHANGE, routing_key='heartbeat.manager') self.test_manager._send_heartbeat(self.test_heartbeat) # By re-declaring the queue again we can get the number of messages # in the queue. res = self.test_rabbit_manager.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=True ) self.assertEqual(1, res.method.message_count) # Check that the message received is actually the HB _, _, body = self.test_rabbit_manager.basic_get( self.test_queue_name) self.assertEqual(self.test_heartbeat, json.loads(body)) except Exception as e: self.fail("Test failed: {}".format(e)) @mock.patch.object(multiprocessing.Process, "start") def test_create_and_start_alerter_process_creates_the_correct_process( self, mock_start) -> None: mock_start.return_value = None self.test_manager._start_alerters_processes() new_entry_process = self.test_manager.alerter_process_dict[ GITHUB_ALERTER_NAME] self.assertTrue(new_entry_process.daemon) self.assertEqual(0, len(new_entry_process._args)) self.assertEqual(start_github_alerter, new_entry_process._target) @mock.patch("src.alerter.alerter_starters.create_logger") def test_start_alerters_process_starts_the_process( self, mock_create_logger) -> None: mock_create_logger.return_value = self.dummy_logger self.test_manager._start_alerters_processes() # We need to sleep to give some time for the alerter to be initialised, # otherwise the process would not terminate time.sleep(1) new_entry_process = self.test_manager.alerter_process_dict[ GITHUB_ALERTER_NAME] self.assertTrue(new_entry_process.is_alive()) new_entry_process.terminate() new_entry_process.join() @freeze_time("2012-01-01") @mock.patch.object(RabbitMQApi, "basic_ack") @mock.patch.object(multiprocessing.Process, "is_alive") @mock.patch.object(multiprocessing.Process, "start") @mock.patch.object(multiprocessing.Process, "join") @mock.patch.object(multiprocessing.Process, "terminate") def test_process_ping_sends_a_valid_hb_if_process_is_alive( self, mock_terminate, mock_join, mock_start, mock_is_alive, mock_ack) -> None: # This test creates a queue which receives messages with the same # routing key as the ones sent by send_heartbeat, and checks that the # received heartbeat is valid. mock_ack.return_value = None mock_is_alive.return_value = True mock_start.return_value = None mock_join.return_value = None mock_terminate.return_value = None try: self.test_manager._initialise_rabbitmq() self.test_manager._start_alerters_processes() # Delete the queue before to avoid messages in the queue on error. self.test_manager.rabbitmq.queue_delete(self.test_queue_name) # initialise blocking_channel = self.test_manager.rabbitmq.channel properties = pika.spec.BasicProperties() method_hb = pika.spec.Basic.Deliver(routing_key='heartbeat.manager') body = 'ping' res = self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=False ) self.assertEqual(0, res.method.message_count) self.test_manager.rabbitmq.queue_bind( queue=self.test_queue_name, exchange=HEALTH_CHECK_EXCHANGE, routing_key='heartbeat.manager') self.test_manager._process_ping(blocking_channel, method_hb, properties, body) # By re-declaring the queue again we can get the number of messages # in the queue. res = self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=True ) self.assertEqual(1, res.method.message_count) expected_output = { "component_name": self.manager_name, "dead_processes": [], "running_processes": [GITHUB_ALERTER_NAME], "timestamp": datetime(2012, 1, 1).timestamp() } # Check that the message received is a valid HB _, _, body = self.test_manager.rabbitmq.basic_get( self.test_queue_name) self.assertEqual(expected_output, json.loads(body)) except Exception as e: self.fail("Test failed: {}".format(e)) @freeze_time("2012-01-01") @mock.patch.object(RabbitMQApi, "basic_ack") @mock.patch.object(multiprocessing.Process, "is_alive") @mock.patch.object(multiprocessing.Process, "start") @mock.patch.object(multiprocessing.Process, "join") @mock.patch.object(multiprocessing.Process, "terminate") def test_process_ping_sends_a_valid_hb_if_process_is_dead( self, mock_terminate, mock_join, mock_start, mock_is_alive, mock_ack) -> None: # This test creates a queue which receives messages with the same # routing key as the ones sent by send_heartbeat, and checks that the # received heartbeat is valid. mock_ack.return_value = None mock_is_alive.return_value = False mock_start.return_value = None mock_join.return_value = None mock_terminate.return_value = None try: self.test_manager._initialise_rabbitmq() self.test_manager._start_alerters_processes() # Delete the queue before to avoid messages in the queue on error. self.test_manager.rabbitmq.queue_delete(self.test_queue_name) # initialise blocking_channel = self.test_manager.rabbitmq.channel properties = pika.spec.BasicProperties() method_hb = pika.spec.Basic.Deliver(routing_key='heartbeat.manager') body = 'ping' res = self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=False ) self.assertEqual(0, res.method.message_count) self.test_manager.rabbitmq.queue_bind( queue=self.test_queue_name, exchange=HEALTH_CHECK_EXCHANGE, routing_key='heartbeat.manager') self.test_manager._process_ping(blocking_channel, method_hb, properties, body) # By re-declaring the queue again we can get the number of messages # in the queue. res = self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=True ) self.assertEqual(1, res.method.message_count) expected_output = { "component_name": self.manager_name, "dead_processes": [GITHUB_ALERTER_NAME], "running_processes": [], "timestamp": datetime(2012, 1, 1).timestamp() } # Check that the message received is a valid HB _, _, body = self.test_manager.rabbitmq.basic_get( self.test_queue_name) self.assertEqual(expected_output, json.loads(body)) except Exception as e: self.fail("Test failed: {}".format(e)) @freeze_time("2012-01-01") @mock.patch.object(RabbitMQApi, "basic_ack") @mock.patch("src.alerter.alerter_starters.create_logger") @mock.patch.object(GithubAlerterManager, "_send_heartbeat") def test_process_ping_restarts_dead_processes( self, send_hb_mock, mock_create_logger, mock_ack) -> None: send_hb_mock.return_value = None mock_create_logger.return_value = self.dummy_logger mock_ack.return_value = None try: self.test_manager._initialise_rabbitmq() self.test_manager._start_alerters_processes() # Give time for the processes to start time.sleep(1) # Automate the case when having all processes dead self.test_manager.alerter_process_dict[ GITHUB_ALERTER_NAME].terminate() self.test_manager.alerter_process_dict[GITHUB_ALERTER_NAME].join() # Give time for the processes to terminate time.sleep(1) # Check that that the processes have terminated self.assertFalse(self.test_manager.alerter_process_dict[ GITHUB_ALERTER_NAME].is_alive()) # initialise blocking_channel = self.test_manager.rabbitmq.channel properties = pika.spec.BasicProperties() method_hb = pika.spec.Basic.Deliver(routing_key='heartbeat.manager') body = 'ping' self.test_manager._process_ping(blocking_channel, method_hb, properties, body) # Give time for the processes to start time.sleep(1) self.assertTrue(self.test_manager.alerter_process_dict[ GITHUB_ALERTER_NAME].is_alive()) # Clean before test finishes self.test_manager.alerter_process_dict[ GITHUB_ALERTER_NAME].terminate() self.test_manager.alerter_process_dict[GITHUB_ALERTER_NAME].join() except Exception as e: self.fail("Test failed: {}".format(e)) @mock.patch.object(multiprocessing.Process, "is_alive") @mock.patch.object(multiprocessing.Process, "start") @mock.patch.object(multiprocessing, 'Process') def test_process_ping_does_not_send_hb_if_processing_fails( self, mock_process, mock_start, is_alive_mock) -> None: # This test creates a queue which receives messages with the same # routing key as the ones sent by send_heartbeat. In this test we will # check that no heartbeat is sent when mocking a raised exception. is_alive_mock.side_effect = self.test_exception mock_start.return_value = None mock_process.side_effect = self.dummy_process1 try: self.test_manager._initialise_rabbitmq() self.test_manager._start_alerters_processes() self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=False ) # Delete the queue before to avoid messages in the queue on error. self.test_manager.rabbitmq.queue_delete(self.test_queue_name) # initialise blocking_channel = self.test_manager.rabbitmq.channel method = pika.spec.Basic.Deliver(routing_key='heartbeat.manager') properties = pika.spec.BasicProperties() body = 'ping' res = self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=False ) self.assertEqual(0, res.method.message_count) self.test_manager.rabbitmq.queue_bind( queue=self.test_queue_name, exchange=HEALTH_CHECK_EXCHANGE, routing_key='heartbeat.manager') self.test_manager._process_ping(blocking_channel, method, properties, body) # By re-declaring the queue again we can get the number of messages # in the queue. res = self.test_manager.rabbitmq.queue_declare( queue=self.test_queue_name, durable=True, exclusive=False, auto_delete=False, passive=True ) self.assertEqual(0, res.method.message_count) except Exception as e: self.fail("Test failed: {}".format(e)) def test_proc_ping_send_hb_does_not_raise_msg_not_del_exce_if_hb_not_routed( self) -> None: try: self.test_manager._initialise_rabbitmq() self.test_manager._start_alerters_processes() # initialise blocking_channel = self.test_manager.rabbitmq.channel method = pika.spec.Basic.Deliver(routing_key='heartbeat.manager') properties = pika.spec.BasicProperties() body = 'ping' self.test_manager._process_ping(blocking_channel, method, properties, body) except Exception as e: self.fail("Test failed: {}".format(e)) @parameterized.expand([ ("pika.exceptions.AMQPChannelError('test')", "pika.exceptions.AMQPChannelError"), ("self.test_exception", "PANICException"), ]) @mock.patch.object(GithubAlerterManager, "_send_heartbeat") def test_process_ping_send_hb_raises_exceptions( self, param_input, param_expected, hb_mock) -> None: hb_mock.side_effect = eval(param_input) try: self.test_manager._initialise_rabbitmq() # initialise blocking_channel = self.test_manager.rabbitmq.channel method = pika.spec.Basic.Deliver(routing_key='heartbeat.manager') properties = pika.spec.BasicProperties() body = 'ping' self.assertRaises(eval(param_expected), self.test_manager._process_ping, blocking_channel, method, properties, body) except Exception as e: self.fail("Test failed: {}".format(e))