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:
@@ -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