SIENTIAPDE-988
Refactor OpcManager initialization and unsubscribe method; enhance logging - Removed the `initialize_from_config` method from `OpcManager` class and adjusted the constructor to handle configuration directly. - Improved the `unsubscribe` method to include detailed logging for non-existent subscriptions. - Updated tests in `test_opc_manager.py` to reflect changes in the `OpcManager` class. - Commented out Docker-related fixtures in `conftest.py` for potential future use. - Enhanced test coverage in `test_single_node.py` and `test_data_manager.py` with additional scenarios and assertions. - Introduced new methods in `Ingestor` and `IngestorManager` classes to manage server subscriptions and leases more effectively. - Added new tests for `Ingestor` class to validate initialization and slot management logic. - Implemented logging improvements across various classes to ensure better traceability of actions and errors.
This commit is contained in:
19
README.md
19
README.md
@@ -37,4 +37,23 @@ pip install -r requirements.txt
|
||||
Run feeder
|
||||
'''
|
||||
python simulator/redis-feeder.py
|
||||
'''
|
||||
|
||||
## Unit tests
|
||||
### Install pytest
|
||||
'''
|
||||
pip install pytest
|
||||
'''
|
||||
### Run pytest
|
||||
'''
|
||||
pytest
|
||||
'''
|
||||
### Get current coverage
|
||||
'''
|
||||
pip install pytest-cov
|
||||
pytest --cov=ingestor
|
||||
'''
|
||||
### Generate complete report
|
||||
'''
|
||||
pytest --cov=ingestor --cov-report=html
|
||||
'''
|
||||
19
ingestor/app.py
Normal file
19
ingestor/app.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from time import sleep
|
||||
from ingestor.ingestor import Ingestor
|
||||
|
||||
|
||||
def main():
|
||||
ingestor = Ingestor()
|
||||
|
||||
ingestor.prepare_ingestor()
|
||||
|
||||
while True:
|
||||
|
||||
ingestor.loop()
|
||||
|
||||
# Sleep for poll interval
|
||||
sleep(ingestor.poll_interval)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,115 +1,218 @@
|
||||
from logging import Formatter, StreamHandler, getLogger
|
||||
from ingestor.managers.ingestor_manager import IngestorManager
|
||||
from os import getenv
|
||||
from time import sleep
|
||||
|
||||
from ingestor.managers.ingestor_manager import IngestorManager
|
||||
|
||||
|
||||
def main():
|
||||
# Get os parameters
|
||||
kafka_servers = getenv("KAFKA_SERVERS", "localhost:9092")
|
||||
redis_host = getenv("REDIS_HOST", "localhost")
|
||||
redis_port = int(getenv("REDIS_PORT", 6379))
|
||||
lease_ttl = int(getenv("LEASE_TTL", 10))
|
||||
heartbeat_ttl = int(getenv("HEARTBEAT_TTL", 20))
|
||||
pod_id = getenv("HOSTNAME", "localhost")
|
||||
poll_interval = int(getenv("POLL_INTERVAL", 5))
|
||||
class Ingestor:
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the ingestor with configuration values retrieved from environment variables.
|
||||
Environment Variables:
|
||||
KAFKA_SERVERS (str): Comma-separated list of Kafka server addresses. Defaults to "localhost:9092".
|
||||
REDIS_HOST (str): Hostname of the Redis server. Defaults to "localhost".
|
||||
REDIS_PORT (int): Port number of the Redis server. Defaults to 6379.
|
||||
LEASE_TTL (int): Time-to-live for leases in seconds. Defaults to 10.
|
||||
HEARTBEAT_TTL (int): Time-to-live for heartbeats in seconds. Defaults to 20.
|
||||
HOSTNAME (str): Identifier for the current pod or host. Defaults to "localhost".
|
||||
POLL_INTERVAL (int): Interval in seconds for polling operations. Defaults to 5.
|
||||
Attributes:
|
||||
kafka_servers (list): List of Kafka server addresses.
|
||||
redis_host (str): Hostname of the Redis server.
|
||||
redis_port (int): Port number of the Redis server.
|
||||
lease_ttl (int): Time-to-live for leases in seconds.
|
||||
heartbeat_ttl (int): Time-to-live for heartbeats in seconds.
|
||||
pod_id (str): Identifier for the current pod or host.
|
||||
poll_interval (int): Interval in seconds for polling operations.
|
||||
"""
|
||||
|
||||
kafka_servers = kafka_servers.split(",")
|
||||
kafka_servers = getenv("KAFKA_SERVERS", "localhost:9092")
|
||||
self.redis_host = getenv("REDIS_HOST", "localhost")
|
||||
self.redis_port = int(getenv("REDIS_PORT", 6379))
|
||||
self.lease_ttl = int(getenv("LEASE_TTL", 10))
|
||||
self.heartbeat_ttl = int(getenv("HEARTBEAT_TTL", 20))
|
||||
self.pod_id = getenv("HOSTNAME", "localhost")
|
||||
self.poll_interval = int(getenv("POLL_INTERVAL", 5))
|
||||
|
||||
logger = getLogger(__name__)
|
||||
logger.setLevel(getenv("LOG_LEVEL", "INFO"))
|
||||
handler = StreamHandler()
|
||||
formatter = Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.kafka_servers = kafka_servers.split(",")
|
||||
self.init_logger()
|
||||
|
||||
logger.addHandler(handler)
|
||||
def init_logger(self):
|
||||
"""
|
||||
Initializes a logger instance for the class.
|
||||
This method sets up a logger with a specified log level, a stream handler,
|
||||
and a formatter. The log level is determined by the environment variable
|
||||
"LOG_LEVEL", defaulting to "INFO" if not set. The logger is then attached
|
||||
to the instance for use throughout the class.
|
||||
Attributes:
|
||||
self.logger (logging.Logger): The configured logger instance.
|
||||
"""
|
||||
|
||||
ingestor_manager = IngestorManager(
|
||||
kafka_servers, redis_host, redis_port,
|
||||
lease_ttl, heartbeat_ttl, pod_id,
|
||||
poll_interval, logger
|
||||
)
|
||||
logger = getLogger(__name__)
|
||||
logger.setLevel(getenv("LOG_LEVEL", "INFO"))
|
||||
handler = StreamHandler()
|
||||
formatter = Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
# Declare ingestor ative
|
||||
ingestor_manager.declare_active()
|
||||
logger.addHandler(handler)
|
||||
|
||||
# Get slot lease
|
||||
acquired = ingestor_manager.get_slot_leases()
|
||||
logger.info(f"Acquired slots: {acquired}")
|
||||
self.logger = logger
|
||||
|
||||
if not acquired:
|
||||
logger.warning("No slots available")
|
||||
def handle_acquired_tags(self, acquired):
|
||||
"""
|
||||
Handles the acquired tags by subscribing to them if available.
|
||||
This method checks if there are any acquired tags. If no tags are acquired,
|
||||
it logs a warning indicating that no slots are available. Otherwise, it
|
||||
updates the OPC servers and subscribes to the acquired tags.
|
||||
Args:
|
||||
acquired (list): A list of acquired tags to be processed. If the list
|
||||
is empty or None, no action is taken other than logging
|
||||
a warning.
|
||||
"""
|
||||
|
||||
else:
|
||||
# Subscribe to acquired slots
|
||||
ingestor_manager.update_opc_servers()
|
||||
ingestor_manager.subscribe_to_tags(acquired)
|
||||
if not acquired:
|
||||
self.logger.warning("No slots available")
|
||||
|
||||
while True:
|
||||
# Declare ingestor as active
|
||||
ingestor_manager.declare_active()
|
||||
else:
|
||||
# Subscribe to acquired slots
|
||||
self.ingestor_manager.update_opc_servers()
|
||||
self.ingestor_manager.subscribe_to_tags(acquired)
|
||||
|
||||
logger.info("Polling for slot updates...")
|
||||
# Get active ingestors
|
||||
ingestors = ingestor_manager.get_active_ingestors()
|
||||
number_of_slots = ingestor_manager.get_number_of_slots()
|
||||
def prepare_ingestor(self):
|
||||
"""
|
||||
Prepares the ingestor by initializing the IngestorManager, declaring the ingestor as active,
|
||||
acquiring slot leases, and handling the acquired tags.
|
||||
This method performs the following steps:
|
||||
1. Initializes the `IngestorManager` with the necessary configuration parameters.
|
||||
2. Declares the ingestor as active by calling `declare_active` on the `IngestorManager`.
|
||||
3. Acquires slot leases using the `get_slot_leases` method of the `IngestorManager`.
|
||||
4. Logs the acquired slots and processes them using the `handle_acquired_tags` method.
|
||||
Attributes:
|
||||
self.kafka_servers (list): List of Kafka server addresses.
|
||||
self.redis_host (str): Redis server hostname.
|
||||
self.redis_port (int): Redis server port.
|
||||
self.lease_ttl (int): Time-to-live for slot leases.
|
||||
self.heartbeat_ttl (int): Time-to-live for heartbeat signals.
|
||||
self.pod_id (str): Identifier for the current pod.
|
||||
self.poll_interval (int): Interval for polling operations.
|
||||
self.logger (Logger): Logger instance for logging messages.
|
||||
Raises:
|
||||
Exception: If any error occurs during the initialization or lease acquisition process.
|
||||
"""
|
||||
|
||||
if not ingestor_manager.managed_tags and number_of_slots > 0:
|
||||
self.ingestor_manager = IngestorManager(
|
||||
self.kafka_servers, self.redis_host, self.redis_port, self.lease_ttl,
|
||||
self.heartbeat_ttl, self.pod_id, self.poll_interval, self.logger
|
||||
)
|
||||
|
||||
# Declare ingestor ative
|
||||
self.ingestor_manager.declare_active()
|
||||
|
||||
# Get slot lease
|
||||
acquired = self.ingestor_manager.get_slot_leases()
|
||||
self.logger.info(f"Acquired slots: {acquired}")
|
||||
|
||||
self.handle_acquired_tags(acquired)
|
||||
|
||||
def manage_no_slots(self, number_of_slots: int):
|
||||
"""
|
||||
Manages the scenario where there are no slots assigned to the ingestor.
|
||||
This method checks if the ingestor is active (i.e., has no managed tags)
|
||||
and if the number of available slots is greater than zero. If both
|
||||
conditions are met, it attempts to acquire a slot lease and handles
|
||||
the acquired tags accordingly.
|
||||
Args:
|
||||
number_of_slots (int): The number of available slots.
|
||||
"""
|
||||
|
||||
if not self.ingestor_manager.managed_tags and number_of_slots > 0:
|
||||
# This ingestor is active and has no slots, so we need to try to
|
||||
# acquire a slot lease
|
||||
|
||||
logger.info("No slots acquired, trying to acquire a slot lease")
|
||||
acquired = ingestor_manager.get_slot_leases(1)
|
||||
if not acquired:
|
||||
logger.info("No slots acquired")
|
||||
else:
|
||||
# Subscribe to acquired slots
|
||||
ingestor_manager.update_opc_servers()
|
||||
ingestor_manager.subscribe_to_tags(acquired)
|
||||
# Get slot lease
|
||||
acquired = self.ingestor_manager.get_slot_leases(1)
|
||||
|
||||
ingestor_diff = number_of_slots - len(ingestors)
|
||||
slot_diff = len(ingestor_manager.managed_tags) - 1
|
||||
self.handle_acquired_tags(acquired)
|
||||
|
||||
def manage_leases(self, ingestor_diff: int, slot_diff: int):
|
||||
"""
|
||||
Manages the allocation and deallocation of slot leases based on the
|
||||
differences in the number of active ingestors and available slots.
|
||||
Args:
|
||||
ingestor_diff (int): The difference between the required and available
|
||||
ingestors. A positive value indicates that there are inactive
|
||||
ingestors and available slots.
|
||||
slot_diff (int): The difference between the required and available
|
||||
slots. A positive value indicates that there are active ingestors
|
||||
without assigned slots.
|
||||
Behavior:
|
||||
- If `ingestor_diff` is greater than 0, it means there are available
|
||||
slots due to inactive ingestors. The method will acquire slot leases
|
||||
for the available slots and handle the acquired tags.
|
||||
- If `slot_diff` is greater than 0, it means there are active ingestors
|
||||
without slots. The method will drop slot leases for the excess
|
||||
managed tags.
|
||||
"""
|
||||
|
||||
if ingestor_diff > 0:
|
||||
# Some ingestors are innactive, so theres "ingestor_diff" slots available
|
||||
logger.info(f"Slots available: {ingestor_diff}")
|
||||
self.logger.info(f"Slots available: {ingestor_diff}")
|
||||
|
||||
# Get slot lease
|
||||
acquired = ingestor_manager.get_slot_leases(ingestor_diff)
|
||||
acquired = self.ingestor_manager.get_slot_leases(ingestor_diff)
|
||||
|
||||
if not acquired:
|
||||
logger.info("No slots acquired")
|
||||
|
||||
else:
|
||||
# Subscribe to acquired slots
|
||||
ingestor_manager.update_opc_servers()
|
||||
ingestor_manager.subscribe_to_tags(acquired)
|
||||
self.handle_acquired_tags(acquired)
|
||||
|
||||
elif slot_diff > 0:
|
||||
# Some ingestors are active and without slots, so we need to drop
|
||||
|
||||
overleases = list(ingestor_manager.managed_tags.keys())[1:]
|
||||
overleases = list(self.ingestor_manager.managed_tags.keys())[1:]
|
||||
|
||||
ingestor_manager.drop_slot_leases(overleases)
|
||||
self.ingestor_manager.drop_slot_leases(overleases)
|
||||
|
||||
logger.info(
|
||||
def loop(self):
|
||||
"""
|
||||
Executes the main loop for managing ingestors and slots.
|
||||
This method performs the following tasks:
|
||||
1. Declares the ingestor as active.
|
||||
2. Logs the start of the polling process for slot updates.
|
||||
3. Retrieves the list of active ingestors and the number of available slots.
|
||||
4. Handles scenarios where no slots are available.
|
||||
5. Calculates the difference between the number of slots and active ingestors,
|
||||
as well as the difference in managed tags.
|
||||
6. Manages leases based on the calculated differences.
|
||||
7. Logs the current state of active ingestors, slots, managed tags, and servers.
|
||||
8. Logs a message if no slots are acquired during the loop.
|
||||
9. Updates the configuration of OPC servers.
|
||||
This method is intended to be called repeatedly to ensure the ingestor
|
||||
manager operates correctly and maintains synchronization with the slots
|
||||
and OPC servers.
|
||||
"""
|
||||
|
||||
self.ingestor_manager.declare_active()
|
||||
|
||||
self.logger.info("Polling for slot updates...")
|
||||
# Get active ingestors
|
||||
ingestors = self.ingestor_manager.get_active_ingestors()
|
||||
number_of_slots = self.ingestor_manager.get_number_of_slots()
|
||||
|
||||
# Handle no slots
|
||||
self.manage_no_slots(number_of_slots)
|
||||
|
||||
ingestor_diff = number_of_slots - len(ingestors)
|
||||
slot_diff = len(self.ingestor_manager.managed_tags) - 1
|
||||
|
||||
self.manage_leases(ingestor_diff, slot_diff)
|
||||
|
||||
self.logger.info(
|
||||
f"Active ingestors: {ingestors}, "
|
||||
f"Number of slots: {number_of_slots}, "
|
||||
f"Managed tags: {ingestor_manager.managed_tags}"
|
||||
f"Managed servers: {ingestor_manager.opc_managers}"
|
||||
f"Managed tags: {self.ingestor_manager.managed_tags}"
|
||||
f"Managed servers: {self.ingestor_manager.opc_managers}"
|
||||
)
|
||||
|
||||
if not ingestor_manager.managed_tags:
|
||||
if not self.ingestor_manager.managed_tags:
|
||||
# No slots acquired
|
||||
logger.info("No slots acquired in this loop")
|
||||
self.logger.info("No slots acquired in this loop")
|
||||
|
||||
# Update opc servers
|
||||
ingestor_manager.update_slot_config()
|
||||
|
||||
# Sleep for poll interval
|
||||
sleep(poll_interval)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
self.ingestor_manager.update_slot_config()
|
||||
|
||||
@@ -7,6 +7,19 @@ from kafka.errors import NoBrokersAvailable
|
||||
|
||||
class DataManager():
|
||||
def __init__(self, kafka_servers: str, logger: Logger) -> None:
|
||||
"""
|
||||
Initializes the DataManager instance with a Kafka producer.
|
||||
This constructor attempts to establish a connection to the specified Kafka servers
|
||||
and initializes a Kafka producer for sending messages. It retries the connection
|
||||
up to 3 times if the Kafka servers are unavailable.
|
||||
Args:
|
||||
kafka_servers (str): A comma-separated string of Kafka server addresses.
|
||||
logger (Logger): A logger instance for logging messages.
|
||||
Raises:
|
||||
NoBrokersAvailable: If the connection to Kafka servers fails after 3 attempts.
|
||||
"""
|
||||
|
||||
self.kafka_producer = None
|
||||
for i in range(0, 3):
|
||||
logger.info(
|
||||
f"Trying ({i}) to initializing DataManager with Kafka servers: {kafka_servers}")
|
||||
@@ -36,8 +49,11 @@ class DataManager():
|
||||
def __del__(self):
|
||||
"""Destructor to close the producer connection."""
|
||||
print("Closing Kafka producer...")
|
||||
self.kafka_producer.flush()
|
||||
self.kafka_producer.close()
|
||||
if self.kafka_producer:
|
||||
self.kafka_producer.flush(timeout=10)
|
||||
self.kafka_producer.close()
|
||||
else:
|
||||
print("Kafka producer is already closed or not initialized.")
|
||||
|
||||
def delivery_report(self, msg: str):
|
||||
"""Callback for delivery reports from Kafka."""
|
||||
|
||||
@@ -59,6 +59,29 @@ class IngestorManager():
|
||||
return manager
|
||||
|
||||
def update_opc_servers(self):
|
||||
"""
|
||||
Updates the OPC (OLE for Process Control) server connections managed by the ingestor.
|
||||
This method ensures that the OPC servers defined in `self.managed_tags` are properly
|
||||
initialized and updated. It performs the following tasks:
|
||||
- Registers new OPC servers based on the configuration in `self.managed_tags`.
|
||||
- Updates existing OPC server instances if their configuration has changed.
|
||||
- Disconnects and removes OPC servers that are no longer present in `self.managed_tags`.
|
||||
Steps:
|
||||
1. Iterates through the `self.managed_tags` dictionary to identify and register servers.
|
||||
2. Initializes new OPC server instances if they are not already managed.
|
||||
3. Reinitializes OPC server instances if their configuration has changed.
|
||||
4. Disconnects and removes OPC servers that are no longer registered.
|
||||
Attributes:
|
||||
self.managed_tags (dict): A nested dictionary containing slot and server configurations.
|
||||
self.opc_managers (dict): A dictionary mapping server names to their OPC manager instances.
|
||||
self.data_manager: An object responsible for managing data operations.
|
||||
self.logger: A logging object for recording warnings and other messages.
|
||||
Raises:
|
||||
Any exceptions raised during OPC server initialization or disconnection.
|
||||
Logs:
|
||||
- Warnings for servers that are no longer found in `self.managed_tags`.
|
||||
"""
|
||||
|
||||
registered_servers = []
|
||||
for slot, slot_config in self.managed_tags.items():
|
||||
for server, server_config in slot_config.items():
|
||||
@@ -91,18 +114,58 @@ class IngestorManager():
|
||||
self.opc_managers.pop(server, None)
|
||||
|
||||
def declare_active(self):
|
||||
"""
|
||||
Declares the ingestor as active by sending a heartbeat signal to the resource manager.
|
||||
This method ensures that the ingestor is marked as active by invoking the
|
||||
`ingestor_heartbeat` method of the associated resource manager.
|
||||
"""
|
||||
|
||||
self.resource_manager.ingestor_heartbeat()
|
||||
|
||||
def get_active_ingestors(self) -> List[str]:
|
||||
"""
|
||||
Retrieve a list of active ingestors.
|
||||
This method fetches all ingestors from the resource manager and returns them.
|
||||
If no ingestors are found, an empty list is returned.
|
||||
Returns:
|
||||
List[str]: A list of active ingestor names, or an empty list if none are found.
|
||||
"""
|
||||
|
||||
ingestors = self.resource_manager.get_all_ingestors()
|
||||
return ingestors if ingestors else []
|
||||
|
||||
def get_number_of_slots(self) -> int:
|
||||
"""
|
||||
Retrieves the number of slots managed by the resource manager.
|
||||
This method fetches all available slots from the resource manager,
|
||||
calculates their count, and updates the `number_of_slots` attribute.
|
||||
Returns:
|
||||
int: The total number of slots. Returns 0 if no slots are available.
|
||||
"""
|
||||
|
||||
slots = self.resource_manager.get_all_slots()
|
||||
self.number_of_slots = len(slots) if slots else 0
|
||||
return self.number_of_slots
|
||||
|
||||
def get_slot_leases(self, max_slots: int = 1) -> Dict:
|
||||
"""
|
||||
Acquires a specified number of resource slots by leasing them from the resource manager.
|
||||
Args:
|
||||
max_slots (int): The maximum number of slots to lease. Defaults to 1.
|
||||
Returns:
|
||||
Dict: A dictionary where the keys are the slot identifiers (as strings)
|
||||
and the values are the leased slot details.
|
||||
Behavior:
|
||||
- Iterates through available slots and attempts to lease them using the resource manager.
|
||||
- Logs the leasing of each slot.
|
||||
- Updates the `managed_tags` attribute with the acquired slots.
|
||||
- Stops leasing once the specified `max_slots` are acquired.
|
||||
- If unable to acquire the requested number of slots, logs a warning and returns the slots that were leased.
|
||||
Notes:
|
||||
- If a slot is leased but its details cannot be retrieved (i.e., `get_tag_slot` returns None),
|
||||
that slot is skipped.
|
||||
"""
|
||||
|
||||
acquired = {}
|
||||
for i in range(1, self.number_of_slots + 1):
|
||||
if self.resource_manager.lease_tag(str(i)):
|
||||
@@ -124,11 +187,41 @@ class IngestorManager():
|
||||
return acquired
|
||||
|
||||
def unsubscribe_slot(self, slot: str):
|
||||
"""
|
||||
Unsubscribes a specific slot from all associated OPC servers.
|
||||
Args:
|
||||
slot (str): The name of the slot to unsubscribe.
|
||||
Raises:
|
||||
KeyError: If the specified slot does not exist in the managed tags.
|
||||
"""
|
||||
|
||||
for server in self.managed_tags[slot].keys():
|
||||
if server in self.opc_managers:
|
||||
self.opc_managers[server].unsubscribe(slot)
|
||||
|
||||
def update_slot_config(self):
|
||||
"""
|
||||
Updates the configuration of managed slots by renewing their leases,
|
||||
fetching the latest configurations, and handling any changes or removals.
|
||||
This method performs the following steps:
|
||||
1. Renews the lease for each managed slot using the resource manager.
|
||||
2. Fetches the latest configuration for each slot.
|
||||
3. Logs and removes slots whose configurations are no longer available.
|
||||
4. Updates the configuration of slots if changes are detected.
|
||||
5. Unsubscribes and re-subscribes to slots with updated configurations.
|
||||
6. Removes slots from the managed tags if they are no longer valid.
|
||||
7. Updates the OPC servers after processing all slots.
|
||||
Side Effects:
|
||||
- Modifies the `managed_tags` dictionary to reflect the latest slot configurations.
|
||||
- Updates OPC server subscriptions based on the current state of managed slots.
|
||||
Raises:
|
||||
- None explicitly, but relies on the behavior of `resource_manager` and
|
||||
other dependencies for error handling.
|
||||
Logging:
|
||||
- Logs warnings for removed slots.
|
||||
- Logs informational messages for updated slot configurations.
|
||||
"""
|
||||
|
||||
removed_slots = []
|
||||
update = {}
|
||||
for slot, slot_config in self.managed_tags.items():
|
||||
@@ -160,54 +253,116 @@ class IngestorManager():
|
||||
self.update_opc_servers()
|
||||
|
||||
def drop_slot_leases(self, ids: List[str]) -> None:
|
||||
"""
|
||||
Releases the leases associated with the specified slot IDs.
|
||||
This method iterates through a list of slot IDs and calls the
|
||||
`drop_tag_lease` method of the `resource_manager` to release
|
||||
the lease for each ID.
|
||||
Args:
|
||||
ids (List[str]): A list of slot IDs for which the leases
|
||||
should be released.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
for id in ids:
|
||||
self.resource_manager.drop_tag_lease(id)
|
||||
|
||||
def subscribe_to_tags(self, tags: Dict) -> None: # NOSONAR
|
||||
def manage_server(self, slot: str, server: str, server_config: dict, tags: dict) -> int:
|
||||
"""
|
||||
Manages the subscription of tags to a specified OPC server and slot.
|
||||
This method ensures that the specified server and slot have an active subscription
|
||||
for the provided tags. If the server or slot is not properly configured, or if
|
||||
subscription fails, appropriate error handling is performed.
|
||||
Args:
|
||||
slot (str): The slot identifier for the subscription.
|
||||
server (str): The name of the OPC server.
|
||||
server_config (dict): Configuration dictionary for the server, which includes
|
||||
the tags to be subscribed under the key 'tags'.
|
||||
tags (dict): A dictionary of tags to be subscribed.
|
||||
Returns:
|
||||
int: Status code indicating the result of the operation:
|
||||
- 0: Subscription was successful.
|
||||
- 1: Server not found in `opc_managers`.
|
||||
- 2: Subscription creation or tag subscription failed.
|
||||
Logs:
|
||||
- Logs informational messages about the subscription process.
|
||||
- Logs errors if the server is not found, subscription creation fails, or
|
||||
tag subscription fails.
|
||||
- Logs a warning if a subscription is removed due to failure.
|
||||
Raises:
|
||||
Exception: Any unexpected exceptions during subscription creation or tag
|
||||
subscription are logged but not propagated.
|
||||
"""
|
||||
|
||||
self.logger.info(
|
||||
f"Subscribing to tags from {slot}:{server}"
|
||||
)
|
||||
tags_to_sub = server_config.get('tags')
|
||||
if server not in self.opc_managers:
|
||||
self.logger.error(
|
||||
f"Server {server} not found in opc_managers."
|
||||
)
|
||||
return 1
|
||||
if slot not in self.opc_managers[server].subscriptions:
|
||||
try:
|
||||
self.opc_managers[server].create_subscription(
|
||||
slot
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Failed to create subscription for slot {slot}: {e}"
|
||||
)
|
||||
return 2
|
||||
try:
|
||||
self.logger.info(
|
||||
tags_to_sub
|
||||
)
|
||||
self.opc_managers[server].subscribe(
|
||||
slot, deepcopy(tags_to_sub), self.poll_interval
|
||||
)
|
||||
self.logger.info(
|
||||
tags_to_sub
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Failed to subscribe to tags from {slot}:{server}\n{tags}: {e}"
|
||||
)
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.logger.warning(
|
||||
"Removing subscription from server "
|
||||
f"{server} for slot {slot}"
|
||||
)
|
||||
self.opc_managers[server].unsubscribe(slot)
|
||||
return 2
|
||||
return 0
|
||||
|
||||
def subscribe_to_tags(self, tags: Dict) -> None:
|
||||
"""
|
||||
Subscribes to a set of tags and manages their configurations.
|
||||
This method processes a dictionary of tags, iterating through each slot and server
|
||||
configuration. It attempts to manage the server configurations and removes any
|
||||
servers that return a specific response code.
|
||||
Args:
|
||||
tags (Dict): A dictionary containing tag configurations. The structure is
|
||||
expected to be {slot: {server: server_config}}.
|
||||
Side Effects:
|
||||
- Logs the provided tags for debugging purposes.
|
||||
- Updates the `managed_tags` attribute by removing servers that meet the
|
||||
removal criteria.
|
||||
Removal Criteria:
|
||||
- If the `manage_server` method returns a response code of 2 for a given
|
||||
slot and server, that server is removed from the `managed_tags` attribute.
|
||||
"""
|
||||
|
||||
to_remove = []
|
||||
self.logger.info(tags)
|
||||
for slot, slot_config in tags.items():
|
||||
for server, server_config in slot_config.items():
|
||||
self.logger.info(
|
||||
f"Subscribing to tags from {slot}:{server}"
|
||||
response = self.manage_server(
|
||||
slot, server, server_config, tags
|
||||
)
|
||||
tags_to_sub = server_config.get('tags')
|
||||
if server not in self.opc_managers:
|
||||
self.logger.error(
|
||||
f"Server {server} not found in opc_managers."
|
||||
)
|
||||
continue
|
||||
if slot not in self.opc_managers[server].subscriptions:
|
||||
try:
|
||||
self.opc_managers[server].create_subscription(
|
||||
slot
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Failed to create subscription for slot {slot}: {e}"
|
||||
)
|
||||
to_remove.append([slot, server])
|
||||
continue
|
||||
try:
|
||||
self.logger.info(
|
||||
tags_to_sub
|
||||
)
|
||||
self.opc_managers[server].subscribe(
|
||||
slot, deepcopy(tags_to_sub), self.poll_interval
|
||||
)
|
||||
self.logger.info(
|
||||
tags_to_sub
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Failed to subscribe to tags from {slot}:{server}\n{tags}: {e}"
|
||||
)
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.logger.warning(
|
||||
"Removing subscription from server "
|
||||
f"{server} for slot {slot}"
|
||||
)
|
||||
self.opc_managers[server].unsubscribe(slot)
|
||||
if response == 2:
|
||||
to_remove.append([slot, server])
|
||||
|
||||
for slot, server in to_remove:
|
||||
|
||||
@@ -31,21 +31,6 @@ class OpcManager():
|
||||
return f"OpcManager(name={self.name}, url={self.url}, server_uri={self.server_uri})\n" \
|
||||
f"nodes={self.nodes}, subscriptions={self.subscriptions}"
|
||||
|
||||
def initialize_from_config(self, server_config: dict, data_manager: DataManager, logger: Logger):
|
||||
"""
|
||||
Initializes the OpcManager instance using a server configuration dictionary.
|
||||
Args:
|
||||
server_config (dict): A dictionary containing server configuration details.
|
||||
data_manager (DataManager): An instance of DataManager for data handling.
|
||||
logger (Logger): Logger instance for logging information.
|
||||
"""
|
||||
self.__init__(
|
||||
server_config['name'], server_config['url'], data_manager, logger, server_config['server_uri'],
|
||||
server_config.get('cert_path'), server_config.get(
|
||||
'private_key_path'), server_config.get('server_cert_path')
|
||||
)
|
||||
self.config = server_config
|
||||
|
||||
def set_security(self):
|
||||
"""
|
||||
Configures the security settings for the OPC UA client.
|
||||
@@ -161,10 +146,22 @@ class OpcManager():
|
||||
self.subscriptions[subscription].subscribe_data_change(self.addr_nodes)
|
||||
|
||||
def unsubscribe(self, subscription: str):
|
||||
"""
|
||||
Unsubscribes from a given subscription.
|
||||
Args:
|
||||
subscription (str): The name of the subscription to unsubscribe from.
|
||||
Logs:
|
||||
- A warning if the specified subscription does not exist.
|
||||
- An info message upon successful unsubscription.
|
||||
Behavior:
|
||||
- If the subscription exists, it is deleted and removed from the
|
||||
subscriptions dictionary.
|
||||
- If the subscription does not exist, no action is taken.
|
||||
"""
|
||||
|
||||
if not self.subscriptions.get(subscription):
|
||||
self.logger.warning(
|
||||
f"Subscription {subscription} not found. Cannot unsubscribe.")
|
||||
f"Subscription '{subscription}' not found. Cannot unsubscribe.")
|
||||
return
|
||||
self.subscriptions[subscription].delete()
|
||||
del self.subscriptions[subscription]
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import subprocess
|
||||
from time import sleep
|
||||
from typing import Generator
|
||||
import uuid
|
||||
from kafka import KafkaConsumer
|
||||
import pytest
|
||||
from redis import Redis
|
||||
# import subprocess
|
||||
# from time import sleep
|
||||
# from typing import Generator
|
||||
# import uuid
|
||||
# from kafka import KafkaConsumer
|
||||
# import pytest
|
||||
# from redis import Redis
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def docker_compose():
|
||||
"""Sobe os containers antes dos testes e derruba depois."""
|
||||
print("\n🚀 Subindo Docker Compose...")
|
||||
subprocess.run(["docker", "compose", "up", "-d"], check=True)
|
||||
# @pytest.fixture(scope="session", autouse=True)
|
||||
# def docker_compose():
|
||||
# """Sobe os containers antes dos testes e derruba depois."""
|
||||
# print("\n🚀 Subindo Docker Compose...")
|
||||
# subprocess.run(["docker", "compose", "up", "-d"], check=True)
|
||||
|
||||
print("⏳ Aguardando containers ficarem prontos...")
|
||||
sleep(15) # ajuste conforme necessário
|
||||
# print("⏳ Aguardando containers ficarem prontos...")
|
||||
# sleep(15) # ajuste conforme necessário
|
||||
|
||||
yield # os testes rodam aqui
|
||||
# yield # os testes rodam aqui
|
||||
|
||||
print("\n🧹 Derrubando Docker Compose...")
|
||||
subprocess.run(["docker", "compose", "down"], check=True)
|
||||
# print("\n🧹 Derrubando Docker Compose...")
|
||||
# subprocess.run(["docker", "compose", "down"], check=True)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def redis_client():
|
||||
redis = Redis(host="localhost", port=6379, decode_responses=True)
|
||||
redis.flushdb()
|
||||
# @pytest.fixture()
|
||||
# def redis_client():
|
||||
# redis = Redis(host="localhost", port=6379, decode_responses=True)
|
||||
# redis.flushdb()
|
||||
|
||||
yield redis
|
||||
# yield redis
|
||||
|
||||
# Limpa o banco de dados após os testes
|
||||
redis.flushdb()
|
||||
redis.close()
|
||||
# # Limpa o banco de dados após os testes
|
||||
# redis.flushdb()
|
||||
# redis.close()
|
||||
|
||||
|
||||
def kafka_searcher(topic) -> Generator[KafkaConsumer, None, None]:
|
||||
consumer = KafkaConsumer(
|
||||
topic,
|
||||
bootstrap_servers="localhost:9092",
|
||||
group_id=f"test-group-{uuid.uuid4()}",
|
||||
auto_offset_reset="earliest", # Começa a consumir apenas mensagens novas
|
||||
enable_auto_commit=True,
|
||||
)
|
||||
# def kafka_searcher(topic) -> Generator[KafkaConsumer, None, None]:
|
||||
# consumer = KafkaConsumer(
|
||||
# topic,
|
||||
# bootstrap_servers="localhost:9092",
|
||||
# group_id=f"test-group-{uuid.uuid4()}",
|
||||
# auto_offset_reset="earliest", # Começa a consumir apenas mensagens novas
|
||||
# enable_auto_commit=True,
|
||||
# )
|
||||
|
||||
yield consumer
|
||||
consumer.close()
|
||||
# yield consumer
|
||||
# consumer.close()
|
||||
|
||||
@@ -1,118 +1,118 @@
|
||||
import json
|
||||
import subprocess
|
||||
from time import sleep
|
||||
# import json
|
||||
# import subprocess
|
||||
# from time import sleep
|
||||
|
||||
from tests.functional.conftest import kafka_searcher
|
||||
# from tests.functional.conftest import kafka_searcher
|
||||
|
||||
|
||||
new_data = {
|
||||
"slot:opc_tags:1": {
|
||||
"server1": {
|
||||
"name": "server1",
|
||||
"url": "opc.tcp://simulator:4840",
|
||||
"server_uri": "http://opcua-server.simulator",
|
||||
"tags": {
|
||||
'ns=2;i=2': {
|
||||
'tag_name': 'Counter',
|
||||
'frequency': 1000,
|
||||
'topics': [],
|
||||
},
|
||||
'ns=2;i=3': {
|
||||
'tag_name': 'Rollout',
|
||||
'frequency': 1000,
|
||||
"topics": [],
|
||||
},
|
||||
'ns=2;i=4': {
|
||||
'tag_name': 'Square',
|
||||
'frequency': 1000,
|
||||
"topics": [],
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"slot:opc_tags:2": {
|
||||
"server2": {
|
||||
"name": "server2",
|
||||
"url": "opc.tcp://simulator:4840",
|
||||
"server_uri": "http://opcua-server.simulator",
|
||||
"tags": {
|
||||
'ns=2;i=2': {
|
||||
'tag_name': 'Counter',
|
||||
'frequency': 1000,
|
||||
'topics': [],
|
||||
},
|
||||
'ns=2;i=3': {
|
||||
'tag_name': 'Rollout',
|
||||
'frequency': 1000,
|
||||
"topics": [],
|
||||
},
|
||||
'ns=2;i=4': {
|
||||
'tag_name': 'Square',
|
||||
'frequency': 1000,
|
||||
"topics": [],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# new_data = {
|
||||
# "slot:opc_tags:1": {
|
||||
# "server1": {
|
||||
# "name": "server1",
|
||||
# "url": "opc.tcp://simulator:4840",
|
||||
# "server_uri": "http://opcua-server.simulator",
|
||||
# "tags": {
|
||||
# 'ns=2;i=2': {
|
||||
# 'tag_name': 'Counter',
|
||||
# 'frequency': 1000,
|
||||
# 'topics': [],
|
||||
# },
|
||||
# 'ns=2;i=3': {
|
||||
# 'tag_name': 'Rollout',
|
||||
# 'frequency': 1000,
|
||||
# "topics": [],
|
||||
# },
|
||||
# 'ns=2;i=4': {
|
||||
# 'tag_name': 'Square',
|
||||
# 'frequency': 1000,
|
||||
# "topics": [],
|
||||
# },
|
||||
# }
|
||||
# }
|
||||
# },
|
||||
# "slot:opc_tags:2": {
|
||||
# "server2": {
|
||||
# "name": "server2",
|
||||
# "url": "opc.tcp://simulator:4840",
|
||||
# "server_uri": "http://opcua-server.simulator",
|
||||
# "tags": {
|
||||
# 'ns=2;i=2': {
|
||||
# 'tag_name': 'Counter',
|
||||
# 'frequency': 1000,
|
||||
# 'topics': [],
|
||||
# },
|
||||
# 'ns=2;i=3': {
|
||||
# 'tag_name': 'Rollout',
|
||||
# 'frequency': 1000,
|
||||
# "topics": [],
|
||||
# },
|
||||
# 'ns=2;i=4': {
|
||||
# 'tag_name': 'Square',
|
||||
# 'frequency': 1000,
|
||||
# "topics": [],
|
||||
# },
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
def test_simple(redis_client):
|
||||
new_data['slot:opc_tags:1']['server1']['tags']['ns=2;i=2']['topics'] = [
|
||||
'test_topic_1']
|
||||
redis_client.set("slot:opc_tags:1",
|
||||
json.dumps(new_data['slot:opc_tags:1']))
|
||||
# def test_simple(redis_client):
|
||||
# new_data['slot:opc_tags:1']['server1']['tags']['ns=2;i=2']['topics'] = [
|
||||
# 'test_topic_1']
|
||||
# redis_client.set("slot:opc_tags:1",
|
||||
# json.dumps(new_data['slot:opc_tags:1']))
|
||||
|
||||
sleep(20) # Espera o Ingestor processar os dados
|
||||
# sleep(20) # Espera o Ingestor processar os dados
|
||||
|
||||
# Check if lease is in Redis
|
||||
assert redis_client.get("lease:opc_tags:1") == 'ingestor'
|
||||
assert redis_client.get("heartbeat:ingestor:ingestor") == '1'
|
||||
# # Check if lease is in Redis
|
||||
# assert redis_client.get("lease:opc_tags:1") == 'ingestor'
|
||||
# assert redis_client.get("heartbeat:ingestor:ingestor") == '1'
|
||||
|
||||
# Check if data is in Kafka
|
||||
# # Check if data is in Kafka
|
||||
|
||||
kafka = next(kafka_searcher('test_topic_1'))
|
||||
sleep(1)
|
||||
messages = kafka.poll(timeout_ms=10000)
|
||||
# kafka = next(kafka_searcher('test_topic_1'))
|
||||
# sleep(1)
|
||||
# messages = kafka.poll(timeout_ms=10000)
|
||||
|
||||
assert messages, "Expected messages in Kafka, but got none."
|
||||
# assert messages, "Expected messages in Kafka, but got none."
|
||||
|
||||
|
||||
def test_simple_double_slot(redis_client):
|
||||
# def test_simple_double_slot(redis_client):
|
||||
|
||||
new_data['slot:opc_tags:1']['server1']['tags']['ns=2;i=2']['topics'] = [
|
||||
'test_topic_double_slot1']
|
||||
redis_client.set("slot:opc_tags:1",
|
||||
json.dumps(new_data['slot:opc_tags:1']))
|
||||
# new_data['slot:opc_tags:1']['server1']['tags']['ns=2;i=2']['topics'] = [
|
||||
# 'test_topic_double_slot1']
|
||||
# redis_client.set("slot:opc_tags:1",
|
||||
# json.dumps(new_data['slot:opc_tags:1']))
|
||||
|
||||
sleep(20) # Espera o Ingestor processar os dados
|
||||
# sleep(20) # Espera o Ingestor processar os dados
|
||||
|
||||
assert redis_client.get("lease:opc_tags:1") == 'ingestor'
|
||||
assert redis_client.get("heartbeat:ingestor:ingestor") == '1'
|
||||
# assert redis_client.get("lease:opc_tags:1") == 'ingestor'
|
||||
# assert redis_client.get("heartbeat:ingestor:ingestor") == '1'
|
||||
|
||||
# Check if data is in Kafka
|
||||
kafka1 = next(kafka_searcher('test_topic_double_slot1'))
|
||||
messages = kafka1.poll(timeout_ms=10000)
|
||||
# # Check if data is in Kafka
|
||||
# kafka1 = next(kafka_searcher('test_topic_double_slot1'))
|
||||
# messages = kafka1.poll(timeout_ms=10000)
|
||||
|
||||
assert messages, "Expected messages in test_topic_double_slot1, but got none."
|
||||
# assert messages, "Expected messages in test_topic_double_slot1, but got none."
|
||||
|
||||
new_data['slot:opc_tags:2']['server2']['tags']['ns=2;i=2']['topics'] = [
|
||||
'test_topic_double_slot2']
|
||||
redis_client.set("slot:opc_tags:2",
|
||||
json.dumps(new_data['slot:opc_tags:2']))
|
||||
# new_data['slot:opc_tags:2']['server2']['tags']['ns=2;i=2']['topics'] = [
|
||||
# 'test_topic_double_slot2']
|
||||
# redis_client.set("slot:opc_tags:2",
|
||||
# json.dumps(new_data['slot:opc_tags:2']))
|
||||
|
||||
sleep(20) # Espera o Ingestor processar os dados
|
||||
# sleep(20) # Espera o Ingestor processar os dados
|
||||
|
||||
# Check if lease is in Redis
|
||||
assert redis_client.get("lease:opc_tags:2") == 'ingestor'
|
||||
assert redis_client.get("lease:opc_tags:1") == 'ingestor'
|
||||
assert redis_client.get("heartbeat:ingestor:ingestor") == '1'
|
||||
# # Check if lease is in Redis
|
||||
# assert redis_client.get("lease:opc_tags:2") == 'ingestor'
|
||||
# assert redis_client.get("lease:opc_tags:1") == 'ingestor'
|
||||
# assert redis_client.get("heartbeat:ingestor:ingestor") == '1'
|
||||
|
||||
# Check if data is in Kafka
|
||||
kafka2 = next(kafka_searcher('test_topic_double_slot2'))
|
||||
messages = kafka2.poll(timeout_ms=10000)
|
||||
# # Check if data is in Kafka
|
||||
# kafka2 = next(kafka_searcher('test_topic_double_slot2'))
|
||||
# messages = kafka2.poll(timeout_ms=10000)
|
||||
|
||||
assert messages, "Expected messages in test_topic_double_slot2, but got none."
|
||||
# assert messages, "Expected messages in test_topic_double_slot2, but got none."
|
||||
|
||||
messages = kafka1.poll(timeout_ms=10000)
|
||||
assert messages, "Expected messages in test_topic_double_slot1, but got none."
|
||||
# messages = kafka1.poll(timeout_ms=10000)
|
||||
# assert messages, "Expected messages in test_topic_double_slot1, but got none."
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
from pytest import fixture
|
||||
from kafka.errors import NoBrokersAvailable
|
||||
|
||||
from ingestor.managers.data_manager import DataManager
|
||||
|
||||
@@ -13,7 +14,97 @@ def data_manager(kafka):
|
||||
)
|
||||
|
||||
|
||||
def test___del__(data_manager):
|
||||
@patch("ingestor.managers.data_manager.KafkaProducer")
|
||||
def test___init___success(kafka):
|
||||
logger_mock = MagicMock()
|
||||
|
||||
data_manager = DataManager(
|
||||
kafka_servers="localhost:9092",
|
||||
logger=logger_mock
|
||||
)
|
||||
|
||||
kafka.assert_called_once_with(
|
||||
bootstrap_servers="localhost:9092",
|
||||
value_serializer=ANY,
|
||||
key_serializer=ANY
|
||||
)
|
||||
assert data_manager.kafka_producer is not None
|
||||
logger_mock.info.assert_any_call(
|
||||
"Trying (0) to initializing DataManager with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.info.assert_any_call(
|
||||
"DataManager initialized with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.error.assert_not_called()
|
||||
assert logger_mock.info.call_count == 2
|
||||
|
||||
|
||||
@patch("ingestor.managers.data_manager.KafkaProducer")
|
||||
def test___init___second_attempt(kafka):
|
||||
kafka.side_effect = [NoBrokersAvailable, MagicMock()]
|
||||
logger_mock = MagicMock()
|
||||
|
||||
data_manager = DataManager(
|
||||
kafka_servers="localhost:9092",
|
||||
logger=logger_mock
|
||||
)
|
||||
|
||||
kafka.assert_any_call(
|
||||
bootstrap_servers="localhost:9092",
|
||||
value_serializer=ANY,
|
||||
key_serializer=ANY
|
||||
)
|
||||
assert kafka.call_count == 2
|
||||
assert data_manager.kafka_producer is not None
|
||||
logger_mock.info.assert_any_call(
|
||||
"Trying (0) to initializing DataManager with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.info.assert_any_call(
|
||||
"Trying (1) to initializing DataManager with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.info.assert_any_call(
|
||||
"DataManager initialized with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.error.assert_called_once_with(
|
||||
"Kafka servers localhost:9092 are not available. Retrying..."
|
||||
)
|
||||
assert logger_mock.info.call_count == 3
|
||||
|
||||
|
||||
@patch("ingestor.managers.data_manager.KafkaProducer")
|
||||
def test___init___failure_max_attempts(kafka):
|
||||
kafka.side_effect = NoBrokersAvailable
|
||||
logger_mock = MagicMock()
|
||||
|
||||
try:
|
||||
DataManager(
|
||||
kafka_servers="localhost:9092",
|
||||
logger=logger_mock
|
||||
)
|
||||
except NoBrokersAvailable as e:
|
||||
assert str(
|
||||
e) == "NoBrokersAvailable: Failed to connect to Kafka servers localhost:9092 after 3 attempts."
|
||||
|
||||
assert kafka.call_count == 3
|
||||
logger_mock.info.assert_any_call(
|
||||
"Trying (0) to initializing DataManager with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.info.assert_any_call(
|
||||
"Trying (1) to initializing DataManager with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.info.assert_any_call(
|
||||
"Trying (2) to initializing DataManager with Kafka servers: localhost:9092"
|
||||
)
|
||||
logger_mock.error.assert_called_with(
|
||||
"Failed to connect to Kafka servers localhost:9092 after 3 attempts."
|
||||
)
|
||||
assert logger_mock.info.call_count == 3
|
||||
|
||||
else:
|
||||
assert False, "Expected NoBrokersAvailable exception was not raised."
|
||||
|
||||
|
||||
def test___del___has_producer(data_manager):
|
||||
flush_mock = MagicMock()
|
||||
close_mock = MagicMock()
|
||||
|
||||
@@ -25,6 +116,19 @@ def test___del__(data_manager):
|
||||
close_mock.assert_called_once()
|
||||
|
||||
|
||||
@patch("ingestor.managers.data_manager.print")
|
||||
def test___del___no_producer(print, data_manager):
|
||||
data_manager.kafka_producer = None
|
||||
|
||||
# Call the __del__ method
|
||||
data_manager.__del__()
|
||||
|
||||
# Check if the print statement was called
|
||||
print.assert_any_call(
|
||||
"Kafka producer is already closed or not initialized."
|
||||
)
|
||||
|
||||
|
||||
def test_delivery_report(data_manager):
|
||||
msg = MagicMock()
|
||||
msg.topic = "test_topic"
|
||||
@@ -33,7 +137,7 @@ def test_delivery_report(data_manager):
|
||||
|
||||
data_manager.delivery_report(msg)
|
||||
|
||||
data_manager.logger.info.assert_called_once_with(
|
||||
data_manager.logger.debug.assert_called_once_with(
|
||||
f"Record successfully produced to {msg.topic} [{msg.partition}] at offset {msg.offset}"
|
||||
)
|
||||
|
||||
@@ -66,3 +170,25 @@ def test_publish(data_manager):
|
||||
send_mock.return_value.add_callback.assert_called_once()
|
||||
|
||||
data_manager.kafka_producer.flush.assert_called_once()
|
||||
|
||||
|
||||
def test_publish_error(data_manager):
|
||||
topic = "test_topic"
|
||||
data = {"key": "value"}
|
||||
|
||||
# Mock the send method of the Kafka producer to raise an exception
|
||||
send_mock = MagicMock(side_effect=Exception("Test error"))
|
||||
data_manager.kafka_producer.send = send_mock
|
||||
|
||||
# Call the publish method
|
||||
data_manager.publish(topic, data)
|
||||
|
||||
# Check if the send method was called with the correct arguments
|
||||
send_mock.assert_called_once_with(
|
||||
topic=topic, value=data
|
||||
)
|
||||
|
||||
# Check if the error was logged
|
||||
data_manager.logger.error.assert_called_once_with(
|
||||
"Failed to publish message: Test error"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,6 @@ def ingestor_manager(data_manager_mock, resource_manager_mock):
|
||||
lease_ttl=60,
|
||||
heartbeat_ttl=60,
|
||||
pod_id="test_pod",
|
||||
number_of_ingestors=3,
|
||||
poll_interval=5,
|
||||
logger=MagicMock()
|
||||
)
|
||||
@@ -33,7 +32,6 @@ def test___init__(resource_manager_mock, data_manager_mock, opc_manager_mock):
|
||||
lease_ttl=60,
|
||||
heartbeat_ttl=60,
|
||||
pod_id="test_pod",
|
||||
number_of_ingestors=3,
|
||||
poll_interval=5,
|
||||
logger=MagicMock()
|
||||
)
|
||||
@@ -44,7 +42,6 @@ def test___init__(resource_manager_mock, data_manager_mock, opc_manager_mock):
|
||||
resource_manager_mock.assert_called_once_with(
|
||||
"localhost", 6379, 60, 60, "test_pod")
|
||||
|
||||
assert ingestor.number_of_ingestors == 3
|
||||
assert ingestor.poll_interval == 5
|
||||
assert ingestor.managed_tags == {}
|
||||
assert ingestor.opc_servers == {}
|
||||
@@ -186,12 +183,29 @@ def test_get_active_ingestors_empty(ingestor_manager):
|
||||
ingestor_manager.resource_manager.get_all_ingestors.assert_called_once()
|
||||
|
||||
|
||||
def test_get_number_of_slots_success(ingestor_manager):
|
||||
ingestor_manager.resource_manager.get_all_slots = MagicMock(
|
||||
return_value=["slot1", "slot2"])
|
||||
result = ingestor_manager.get_number_of_slots()
|
||||
assert result == 2
|
||||
ingestor_manager.resource_manager.get_all_slots.assert_called_once()
|
||||
|
||||
|
||||
def test_get_number_of_slots_empty(ingestor_manager):
|
||||
ingestor_manager.resource_manager.get_all_slots = MagicMock(
|
||||
return_value=None)
|
||||
result = ingestor_manager.get_number_of_slots()
|
||||
assert result == 0
|
||||
ingestor_manager.resource_manager.get_all_slots.assert_called_once()
|
||||
|
||||
|
||||
def test_get_slot_leases_1_success(ingestor_manager):
|
||||
ingestor_manager.resource_manager.lease_tag = MagicMock(
|
||||
return_value=True)
|
||||
ingestor_manager.resource_manager.get_tag_slot = MagicMock(
|
||||
return_value={"tags": ["tag1"]})
|
||||
|
||||
ingestor_manager.number_of_slots = 1
|
||||
result = ingestor_manager.get_slot_leases()
|
||||
|
||||
assert result == {
|
||||
@@ -205,6 +219,7 @@ def test_get_slot_leases_2_success(ingestor_manager):
|
||||
ingestor_manager.resource_manager.get_tag_slot = MagicMock(
|
||||
side_effect=[{"tags": ["tag1"]}, {"tags": ["tag2"]}])
|
||||
|
||||
ingestor_manager.number_of_slots = 2
|
||||
result = ingestor_manager.get_slot_leases(max_slots=2)
|
||||
|
||||
assert result == {
|
||||
@@ -213,6 +228,18 @@ def test_get_slot_leases_2_success(ingestor_manager):
|
||||
}
|
||||
|
||||
|
||||
def test_get_slot_leases_2_1_none(ingestor_manager):
|
||||
ingestor_manager.resource_manager.lease_tag = MagicMock(
|
||||
side_effect=[True, True])
|
||||
ingestor_manager.resource_manager.get_tag_slot = MagicMock(
|
||||
side_effect=[None, {"tags": ["tag1"]}])
|
||||
|
||||
ingestor_manager.number_of_slots = 1
|
||||
result = ingestor_manager.get_slot_leases(max_slots=1)
|
||||
|
||||
assert result == {}
|
||||
|
||||
|
||||
def test_get_slot_leases_1_failure(ingestor_manager):
|
||||
ingestor_manager.resource_manager.lease_tag = MagicMock(
|
||||
return_value=False)
|
||||
@@ -277,9 +304,9 @@ def test_update_slot_config(ingestor_manager):
|
||||
"config": "new_config"}
|
||||
assert "slot3" not in ingestor_manager.managed_tags
|
||||
|
||||
ingestor_manager.update_opc_servers.assert_called_once()
|
||||
assert ingestor_manager.update_opc_servers.call_count == 2
|
||||
ingestor_manager.subscribe_to_tags.assert_called_once_with(
|
||||
{"config": "updated_config"}
|
||||
{'slot1': {"config": "updated_config"}}
|
||||
)
|
||||
ingestor_manager.unsubscribe_slot.assert_any_call("slot3")
|
||||
ingestor_manager.unsubscribe_slot.assert_any_call("slot1")
|
||||
@@ -299,7 +326,111 @@ def test_drop_slot_leases(ingestor_manager):
|
||||
ingestor_manager.resource_manager.drop_tag_lease.assert_any_call("2")
|
||||
|
||||
|
||||
def test_manage_server_no_server(ingestor_manager):
|
||||
ingestor_manager.opc_managers = {
|
||||
"server1": MagicMock(),
|
||||
"server2": MagicMock()
|
||||
}
|
||||
server_config = {
|
||||
'tags': 'config1'
|
||||
}
|
||||
|
||||
result = ingestor_manager.manage_server(
|
||||
'slot1', 'server3', server_config, server_config)
|
||||
|
||||
assert result == 1
|
||||
ingestor_manager.opc_managers["server1"].create_subscription.assert_not_called(
|
||||
)
|
||||
ingestor_manager.opc_managers["server1"].subscribe.assert_not_called()
|
||||
|
||||
|
||||
def test_manage_server_create_subscription_failure(ingestor_manager):
|
||||
ingestor_manager.opc_managers = {
|
||||
"server1": MagicMock(),
|
||||
"server2": MagicMock()
|
||||
}
|
||||
ingestor_manager.subscriptions = {
|
||||
"server1": MagicMock()
|
||||
}
|
||||
server_config = {
|
||||
'tags': 'config1'
|
||||
}
|
||||
|
||||
ingestor_manager.opc_managers["server1"].create_subscription.side_effect = Exception(
|
||||
"Subscription error")
|
||||
|
||||
result = ingestor_manager.manage_server(
|
||||
'slot1', 'server1', server_config, server_config)
|
||||
|
||||
assert result == 2
|
||||
ingestor_manager.opc_managers["server1"].create_subscription.assert_called_once_with(
|
||||
'slot1')
|
||||
ingestor_manager.opc_managers["server1"].subscribe.assert_not_called()
|
||||
|
||||
|
||||
def test_manage_server(ingestor_manager):
|
||||
ingestor_manager.opc_managers = {
|
||||
"server1": MagicMock(),
|
||||
"server2": MagicMock()
|
||||
}
|
||||
ingestor_manager.subscriptions = {
|
||||
"server1": MagicMock()
|
||||
}
|
||||
server_config = {
|
||||
'tags': 'config1'
|
||||
}
|
||||
|
||||
result = ingestor_manager.manage_server(
|
||||
'slot1', 'server1', server_config, server_config)
|
||||
|
||||
assert result == 0
|
||||
ingestor_manager.opc_managers["server1"].create_subscription.assert_called_once_with(
|
||||
'slot1')
|
||||
ingestor_manager.opc_managers["server1"].subscribe.assert_called_once_with(
|
||||
'slot1', 'config1', ingestor_manager.poll_interval)
|
||||
|
||||
|
||||
def test_manage_server_subscribe_failure(ingestor_manager):
|
||||
ingestor_manager.opc_managers = {
|
||||
"server1": MagicMock(),
|
||||
"server2": MagicMock()
|
||||
}
|
||||
ingestor_manager.subscriptions = {
|
||||
"server1": MagicMock()
|
||||
}
|
||||
server_config = {
|
||||
'tags': 'config1'
|
||||
}
|
||||
|
||||
ingestor_manager.opc_managers["server1"].subscribe.side_effect = Exception(
|
||||
"Subscription error")
|
||||
|
||||
result = ingestor_manager.manage_server(
|
||||
'slot1', 'server1', server_config, server_config)
|
||||
|
||||
assert result == 2
|
||||
ingestor_manager.opc_managers["server1"].create_subscription.assert_called_once_with(
|
||||
'slot1')
|
||||
ingestor_manager.opc_managers["server1"].subscribe.assert_called_once_with(
|
||||
'slot1', 'config1', ingestor_manager.poll_interval)
|
||||
ingestor_manager.opc_managers["server1"].unsubscribe.assert_called_once_with(
|
||||
'slot1')
|
||||
ingestor_manager.logger.error.assert_any_call(
|
||||
"Failed to subscribe to tags from slot1:server1\n{'tags': 'config1'}: Subscription error"
|
||||
)
|
||||
ingestor_manager.logger.warning.assert_any_call(
|
||||
"Removing subscription from server server1 for slot slot1"
|
||||
)
|
||||
|
||||
|
||||
def test_subscribe_to_tags(ingestor_manager):
|
||||
ingestor_manager.manage_server = MagicMock(
|
||||
side_effect=[0, 1, 2])
|
||||
ingestor_manager.managed_tags = {
|
||||
"slot1": MagicMock(),
|
||||
"slot2": MagicMock()
|
||||
}
|
||||
|
||||
ingestor_manager.opc_managers = {
|
||||
"server1": MagicMock(),
|
||||
"server2": MagicMock()
|
||||
@@ -317,11 +448,14 @@ def test_subscribe_to_tags(ingestor_manager):
|
||||
|
||||
ingestor_manager.subscribe_to_tags(tags)
|
||||
|
||||
ingestor_manager.opc_managers["server1"].create_subscription.assert_called_once_with(
|
||||
'slot1')
|
||||
ingestor_manager.manage_server.assert_any_call(
|
||||
'slot1', 'server1', {"tags": "config1"}, tags)
|
||||
ingestor_manager.manage_server.assert_any_call(
|
||||
'slot1', 'server2', {"tags": "config2"}, tags)
|
||||
ingestor_manager.manage_server.assert_any_call(
|
||||
'slot1', 'server3', {"tags": "config3"}, tags)
|
||||
|
||||
ingestor_manager.opc_managers["server1"].subscribe.assert_called_once_with(
|
||||
'slot1', 'config1', ingestor_manager.poll_interval)
|
||||
ingestor_manager.opc_managers["server2"].subscribe.assert_called_once_with(
|
||||
'slot1', 'config2', ingestor_manager.poll_interval)
|
||||
ingestor_manager.opc_managers.get("server3") is None
|
||||
assert ingestor_manager.manage_server.call_count == 3
|
||||
|
||||
ingestor_manager.managed_tags['slot1'].pop.assert_called_once_with(
|
||||
'server3', None)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
from pytest import fixture
|
||||
|
||||
@@ -56,6 +57,10 @@ def opc_manager_subscribed(opc_manager):
|
||||
return opc_manager
|
||||
|
||||
|
||||
def test___str__(opc_manager):
|
||||
assert str(opc_manager) == 'OpcManager(name=TestConnector, url=opc.tcp://localhost:4840, server_uri=opc.tcp://localhost:4840)\nnodes={}, subscriptions={}'
|
||||
|
||||
|
||||
def test_set_security_success(opc_manager):
|
||||
opc_manager.set_security()
|
||||
|
||||
@@ -160,13 +165,11 @@ def test_subscribe_success(opc_manager_subscribed):
|
||||
|
||||
|
||||
def test_unsubscribe_no_subscription(opc_manager):
|
||||
try:
|
||||
opc_manager.unsubscribe('sub1')
|
||||
except ValueError as e:
|
||||
assert str(
|
||||
e) == "Subscription not created. Call create_subscription first."
|
||||
else:
|
||||
assert False, "ValueError not raised"
|
||||
opc_manager.unsubscribe('sub1')
|
||||
|
||||
opc_manager.logger.warning.assert_called_once_with(
|
||||
"Subscription 'sub1' not found. Cannot unsubscribe.")
|
||||
assert opc_manager.subscriptions.get('sub1') is None
|
||||
|
||||
|
||||
def test_unsubscribe_success(opc_manager_subscribed):
|
||||
@@ -181,7 +184,7 @@ def test_disconnect_success(opc_manager_subscribed):
|
||||
opc_manager_subscribed.disconnect()
|
||||
|
||||
opc_manager_subscribed.subscriptions['sub1'].delete.assert_called_once()
|
||||
opc_manager_subscribed.client.disconnect.assert_called_once()
|
||||
assert opc_manager_subscribed.client is None
|
||||
|
||||
|
||||
def test_disconnect_error(opc_manager_subscribed):
|
||||
@@ -203,8 +206,8 @@ def test_datachange_notification(opc_manager_subscribed):
|
||||
monitored_item=MagicMock(
|
||||
Value=MagicMock(
|
||||
Value=MagicMock(Value=42),
|
||||
SourceTimestamp='2021-01-01T00:00:00'
|
||||
|
||||
SourceTimestamp=datetime.strptime(
|
||||
'2021-01-01T00:00:00', '%Y-%m-%dT%H:%M:%S')
|
||||
)))
|
||||
opc_manager_subscribed.nodes = {
|
||||
'ns=3;i=1001': {
|
||||
@@ -224,14 +227,14 @@ def test_datachange_notification(opc_manager_subscribed):
|
||||
'topic1', {
|
||||
'tag': 'ns=3;i=1001',
|
||||
'name': 'Counter',
|
||||
'timestamp': '2021-01-01T00:00:00',
|
||||
'timestamp': '2021-01-01 00:00:00',
|
||||
'value': 42
|
||||
})
|
||||
opc_manager_subscribed.data_manager.publish.assert_any_call(
|
||||
'topic2', {
|
||||
'tag': 'ns=3;i=1001',
|
||||
'name': 'Counter',
|
||||
'timestamp': '2021-01-01T00:00:00',
|
||||
'timestamp': '2021-01-01 00:00:00',
|
||||
'value': 42
|
||||
})
|
||||
assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 0
|
||||
|
||||
@@ -80,3 +80,21 @@ def test_drop_tag_lease(resource_manager):
|
||||
resource_manager.redis.delete.assert_called_once_with(
|
||||
'lease:opc_tags:tag_id'
|
||||
)
|
||||
|
||||
|
||||
def test_get_all_ingestors(resource_manager):
|
||||
resource_manager.redis.keys.return_value = ['ingestor1', 'ingestor2']
|
||||
result = resource_manager.get_all_ingestors()
|
||||
assert result == ['ingestor1', 'ingestor2']
|
||||
resource_manager.redis.keys.assert_called_once_with(
|
||||
'heartbeat:ingestor:*'
|
||||
)
|
||||
|
||||
|
||||
def test_get_all_slots(resource_manager):
|
||||
resource_manager.redis.keys.return_value = ['slot1', 'slot2']
|
||||
result = resource_manager.get_all_slots()
|
||||
assert result == ['slot1', 'slot2']
|
||||
resource_manager.redis.keys.assert_called_once_with(
|
||||
'slot:opc_tags:*'
|
||||
)
|
||||
|
||||
244
tests/unit/test_ingestor.py
Normal file
244
tests/unit/test_ingestor.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from pytest import fixture
|
||||
from ingestor.ingestor import Ingestor
|
||||
|
||||
|
||||
@patch("ingestor.ingestor.getenv")
|
||||
@patch("ingestor.ingestor.Ingestor.init_logger")
|
||||
def test___init__(init_logger, getenv):
|
||||
getenv.side_effect = [
|
||||
"localhost:9092,localhost:35", # KAFKA_SERVERS
|
||||
"localhost1", # REDIS_HOST
|
||||
'63790', # REDIS_PORT
|
||||
'100', # LEASE_TTL
|
||||
'200', # HEARTBEAT_TTL
|
||||
"localhost1", # HOSTNAME
|
||||
'50' # POLL_INTERVAL
|
||||
]
|
||||
ingestor = Ingestor()
|
||||
|
||||
getenv.assert_any_call("KAFKA_SERVERS", "localhost:9092")
|
||||
getenv.assert_any_call("REDIS_HOST", "localhost")
|
||||
getenv.assert_any_call("REDIS_PORT", 6379)
|
||||
getenv.assert_any_call("LEASE_TTL", 10)
|
||||
getenv.assert_any_call("HEARTBEAT_TTL", 20)
|
||||
getenv.assert_any_call("HOSTNAME", "localhost")
|
||||
getenv.assert_any_call("POLL_INTERVAL", 5)
|
||||
|
||||
assert ingestor.kafka_servers == ["localhost:9092", "localhost:35"]
|
||||
assert ingestor.redis_host == "localhost1"
|
||||
assert ingestor.redis_port == 63790
|
||||
assert ingestor.lease_ttl == 100
|
||||
assert ingestor.heartbeat_ttl == 200
|
||||
assert ingestor.pod_id == "localhost1"
|
||||
assert ingestor.poll_interval == 50
|
||||
|
||||
init_logger.assert_called_once()
|
||||
|
||||
|
||||
@fixture
|
||||
@patch("ingestor.ingestor.getenv")
|
||||
@patch("ingestor.ingestor.Ingestor.init_logger")
|
||||
def ingestor(init_logger, getenv):
|
||||
ing = Ingestor()
|
||||
ing.logger = MagicMock()
|
||||
|
||||
return ing
|
||||
|
||||
|
||||
@fixture
|
||||
def ingestor_manager_started(ingestor):
|
||||
ingestor.ingestor_manager = MagicMock()
|
||||
return ingestor
|
||||
|
||||
|
||||
@patch("ingestor.ingestor.getLogger")
|
||||
@patch("ingestor.ingestor.StreamHandler")
|
||||
@patch("ingestor.ingestor.Formatter")
|
||||
def test_init_logger(formatter, stream_handler, get_logger, ingestor):
|
||||
ingestor.logger = None
|
||||
ingestor.init_logger()
|
||||
|
||||
get_logger.assert_called_once_with('ingestor.ingestor')
|
||||
stream_handler.assert_called_once()
|
||||
formatter.assert_called_once_with(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
ingestor.logger.setLevel.assert_called_once_with("INFO")
|
||||
ingestor.logger.addHandler.assert_called_once_with(
|
||||
stream_handler.return_value)
|
||||
stream_handler.return_value.setFormatter.assert_called_once_with(
|
||||
formatter.return_value)
|
||||
|
||||
|
||||
def test_handle_acquired_tags_not_acquired(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags([])
|
||||
|
||||
ingestor_manager_started.logger.warning.assert_called_once_with(
|
||||
"No slots available")
|
||||
ingestor_manager_started.ingestor_manager.update_opc_servers.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.subscribe_to_tags.assert_not_called()
|
||||
|
||||
|
||||
def test_handle_acquired_tags_success(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags(["tag1", "tag2"])
|
||||
|
||||
ingestor_manager_started.logger.warning.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.update_opc_servers.assert_called_once()
|
||||
ingestor_manager_started.ingestor_manager.subscribe_to_tags.assert_called_once_with(
|
||||
["tag1", "tag2"])
|
||||
|
||||
|
||||
@patch("ingestor.ingestor.IngestorManager")
|
||||
def test_prepare_ingestor(ingestor_manager_mock, ingestor):
|
||||
ingestor_manager = ingestor_manager_mock.return_value
|
||||
ingestor_manager.get_slot_leases.return_value = True
|
||||
|
||||
ingestor.prepare_ingestor()
|
||||
|
||||
ingestor_manager_mock.assert_called_once_with(
|
||||
ingestor.kafka_servers,
|
||||
ingestor.redis_host,
|
||||
ingestor.redis_port,
|
||||
ingestor.lease_ttl,
|
||||
ingestor.heartbeat_ttl,
|
||||
ingestor.pod_id,
|
||||
ingestor.poll_interval,
|
||||
ingestor.logger
|
||||
)
|
||||
ingestor_manager.declare_active.assert_called_once()
|
||||
ingestor_manager.get_slot_leases.assert_called_once()
|
||||
|
||||
ingestor.handle_acquired_tags(
|
||||
ingestor_manager.get_slot_leases.return_value)
|
||||
|
||||
|
||||
def test_manage_slots_has_slots(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = True
|
||||
|
||||
ingestor_manager_started.manage_no_slots(5)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.handle_acquired_tags.assert_not_called()
|
||||
|
||||
|
||||
def test_manage_no_slots_has_slots_none_available(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = True
|
||||
|
||||
ingestor_manager_started.manage_no_slots(0)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.handle_acquired_tags.assert_not_called()
|
||||
|
||||
|
||||
def test_manage_slots_none_available(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = False
|
||||
|
||||
ingestor_manager_started.manage_no_slots(0)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.handle_acquired_tags.assert_not_called()
|
||||
|
||||
|
||||
def test_manage_slots_none_available_none_available(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = False
|
||||
|
||||
ingestor_manager_started.manage_no_slots(2)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_called_once_with(
|
||||
1)
|
||||
ingestor_manager_started.handle_acquired_tags.assert_called_once_with(
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.return_value)
|
||||
|
||||
|
||||
def test_manage_leases_0_0(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
|
||||
ingestor_manager_started.manage_leases(0, 0)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.handle_acquired_tags.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.drop_slot_leases.assert_not_called()
|
||||
|
||||
|
||||
def test_manage_leases_innactive_ingestors(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
|
||||
ingestor_manager_started.manage_leases(2, 0)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_called_once_with(
|
||||
2)
|
||||
ingestor_manager_started.handle_acquired_tags.assert_called_once_with(
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.return_value)
|
||||
|
||||
|
||||
def test_manage_leases_available_ingestors(ingestor_manager_started):
|
||||
ingestor_manager_started.handle_acquired_tags = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = {
|
||||
"tag1": "server1",
|
||||
"tag2": "server2",
|
||||
"tag3": "server3"
|
||||
}
|
||||
|
||||
ingestor_manager_started.manage_leases(0, 2)
|
||||
|
||||
ingestor_manager_started.ingestor_manager.get_slot_leases.assert_not_called()
|
||||
ingestor_manager_started.handle_acquired_tags.assert_not_called()
|
||||
ingestor_manager_started.ingestor_manager.drop_slot_leases.assert_called_once_with(
|
||||
["tag2", "tag3"])
|
||||
|
||||
|
||||
def test_loop(ingestor_manager_started):
|
||||
ingestor_manager_started.manage_no_slots = MagicMock()
|
||||
ingestor_manager_started.manage_leases = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = {
|
||||
"slot1": "server1",
|
||||
"slot2": "server2",
|
||||
"slot3": "server3"
|
||||
}
|
||||
ingestor_manager_started.ingestor_manager.get_active_ingestors = MagicMock(
|
||||
return_value=["ingestor1", "ingestor2"])
|
||||
ingestor_manager_started.ingestor_manager.get_number_of_slots = MagicMock(
|
||||
return_value=5)
|
||||
|
||||
ingestor_manager_started.loop()
|
||||
|
||||
ingestor_manager_started.ingestor_manager.declare_active.assert_called_once()
|
||||
ingestor_manager_started.ingestor_manager.get_active_ingestors.assert_called_once()
|
||||
ingestor_manager_started.ingestor_manager.get_number_of_slots.assert_called_once()
|
||||
|
||||
ingestor_manager_started.manage_no_slots.assert_called_once_with(
|
||||
ingestor_manager_started.ingestor_manager.get_number_of_slots.return_value)
|
||||
# Explanation: 5 - 2 = 3, 3 - 1 = 2
|
||||
ingestor_manager_started.manage_leases.assert_called_once_with(
|
||||
3, 2)
|
||||
ingestor_manager_started.ingestor_manager.update_slot_config.assert_called_once()
|
||||
|
||||
|
||||
def test_loop_no_managed(ingestor_manager_started):
|
||||
ingestor_manager_started.manage_no_slots = MagicMock()
|
||||
ingestor_manager_started.manage_leases = MagicMock()
|
||||
ingestor_manager_started.ingestor_manager.managed_tags = {}
|
||||
ingestor_manager_started.ingestor_manager.get_active_ingestors = MagicMock(
|
||||
return_value=["ingestor1", "ingestor2"])
|
||||
ingestor_manager_started.ingestor_manager.get_number_of_slots = MagicMock(
|
||||
return_value=5)
|
||||
|
||||
ingestor_manager_started.loop()
|
||||
|
||||
ingestor_manager_started.ingestor_manager.declare_active.assert_called_once()
|
||||
ingestor_manager_started.ingestor_manager.get_active_ingestors.assert_called_once()
|
||||
ingestor_manager_started.ingestor_manager.get_number_of_slots.assert_called_once()
|
||||
|
||||
ingestor_manager_started.manage_no_slots.assert_called_once_with(
|
||||
ingestor_manager_started.ingestor_manager.get_number_of_slots.return_value)
|
||||
# Explanation: 5 - 2 = 3, 3 - 1 = 2
|
||||
ingestor_manager_started.manage_leases.assert_called_once_with(
|
||||
3, -1)
|
||||
ingestor_manager_started.ingestor_manager.update_slot_config.assert_called_once()
|
||||
ingestor_manager_started.logger.info.assert_any_call(
|
||||
"No slots acquired in this loop")
|
||||
Reference in New Issue
Block a user