SIENTIAPDE-988

Enhance ingestor and manager classes with notification handling

- Integrated NotificationHandler into Ingestor, DataManager, IngestorManager, and OpcManager for improved error reporting and monitoring.
- Updated methods to send notifications on critical events such as Kafka publishing errors, OPC connection issues, and cycle count warnings.
- Refactored related tests to ensure coverage of new notification functionalities and validate integration with existing components.
- Improved logging and error handling across the system to enhance traceability and operational insights.
This commit is contained in:
vitor-aignosi
2025-04-30 19:28:43 -03:00
parent 3fc3753636
commit d00078d323
8 changed files with 423 additions and 168 deletions

View File

@@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch
from pytest import fixture
from asyncua.crypto.security_policies import SecurityPolicyBasic256
import pytest
from ingestor.managers.opc_manager import OpcManager
from sientia_do.notifications.models import NotificationLevel
@@ -36,7 +37,7 @@ tags = {
def raw_opc_manager():
return OpcManager(
'TestConnector', 'opc.tcp://localhost:4840', MagicMock(),
MagicMock(), 'opc.tcp://localhost:4840'
MagicMock(), 'opc.tcp://localhost:4840', MagicMock()
)
@@ -240,82 +241,102 @@ def test_datachange_notification(opc_manager_subscribed):
assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 0
def test_check_cycles(opc_manager_subscribed):
handler = MagicMock()
opc_manager_subscribed.nodes = tags
opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule'] = {
'cycle_increment': 1.0,
'cycle_count': 0
def test_check_cycles_no_notification(opc_manager):
# Setup: node with cycle_count just below threshold
opc_manager.nodes = {
'ns=3;i=1001': {
'tag_name': 'Counter',
'cycle_rule': {
'cycle_increment': 1.0,
'cycle_count': 3.0
}
}
}
opc_manager_subscribed.check_cycles(
{}, 'ns=3;i=1001', tags['ns=3;i=1001'], handler)
opc_manager.notification_handler.build_and_send_notification = MagicMock()
assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 1
opc_manager.check_cycles()
opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] = 0
opc_manager_subscribed.check_cycles({'ns=3;i=1001': {}},
'ns=3;i=1001', tags['ns=3;i=1001'], handler)
assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 0
opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] = 4
opc_manager_subscribed.check_cycles(
{}, 'ns=3;i=1001', tags['ns=3;i=1001'], handler)
assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 5
handler.assert_called_once_with('5.0 cycles without receive from ns=3;i=1001:Counter',
'TAG_ns=3;i=1001:Counter_LISTENNING_STOPPED', NotificationLevel.WARNING)
# After one increment, cycle_count = 4.0, still below threshold
assert opc_manager.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == pytest.approx(
4.0)
opc_manager.notification_handler.build_and_send_notification.assert_not_called()
def test_check_opc_listenning_5_cycles(opc_manager_subscribed):
def test_check_cycles_triggers_notification(opc_manager):
# Setup: node with cycle_count just below threshold, increment will cross threshold
opc_manager.nodes = {
'ns=3;i=1001': {
'tag_name': 'Counter',
'cycle_rule': {
'cycle_increment': 2.5,
'cycle_count': 3.0
}
}
}
opc_manager.notification_handler.build_and_send_notification = MagicMock()
handler = MagicMock()
opc_manager_subscribed.nodes = tags
opc_manager_subscribed.non_receive_count = 4
opc_manager.check_cycles()
opc_manager_subscribed.check_opc_listenning(handler)
handler.assert_called_once_with(
f'5 cycles without receive from OPC TestConnector. Tags: {json.dumps(tags)}',
'OPC_LISTENNING_STOPPED__TestConnector',
NotificationLevel.ERROR
# After increment, cycle_count = 5.5, should trigger notification
assert opc_manager.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == pytest.approx(
5.5)
opc_manager.notification_handler.build_and_send_notification.assert_called_once_with(
notification_id='TAG_ns=3;i=1001:Counter_LISTENNING_STOPPED',
message='5.5 cycles without receive from ns=3;i=1001:Counter',
block="opc_manager",
level=NotificationLevel.WARNING
)
def test_check_opc_listenning_no_cycles(opc_manager_subscribed):
handler = MagicMock()
opc_manager_subscribed.non_receive_count = 0
def test_check_opc_listenning_no_notification(opc_manager):
opc_manager.non_receive_count = 3
opc_manager.notification_handler.build_and_send_notification = MagicMock()
opc_manager_subscribed.check_opc_listenning(handler)
result = opc_manager.check_opc_listenning()
handler.assert_not_called()
assert opc_manager.non_receive_count == 4
opc_manager.notification_handler.build_and_send_notification.assert_not_called()
assert result is False
def test_check_opc_listenning_no_handler(opc_manager_subscribed):
opc_manager_subscribed.non_receive_count = 5
opc_manager_subscribed.check_opc_listenning(None)
def test_check_opc_listenning_warning_notification(opc_manager):
opc_manager.non_receive_count = 4
opc_manager.notification_handler.build_and_send_notification = MagicMock()
assert opc_manager_subscribed.non_receive_count == 6
result = opc_manager.check_opc_listenning()
def test_check_opc_listenning_15_cycles(opc_manager_subscribed):
handler = MagicMock()
opc_manager_subscribed.init_collector = MagicMock()
opc_manager_subscribed.non_receive_count = 14
opc_manager_subscribed.collect_period = 1000
opc_manager_subscribed.period = 500
opc_manager_subscribed.nodes = tags
opc_manager_subscribed.check_opc_listenning(handler)
handler.assert_any_call(
f'15 cycles without receive from OPC TestConnector. Tags: {json.dumps(tags)}',
'OPC_LISTENNING_STOPPED__TestConnector',
NotificationLevel.ERROR
assert opc_manager.non_receive_count == 5
opc_manager.notification_handler.build_and_send_notification.assert_called_once_with(
notification_id=f'OPC_LISTENNING_STOPPED__{opc_manager.name}',
message=f'5 cycles without receive from OPC {opc_manager.name}. Tags: {json.dumps(opc_manager.nodes)}',
block="opc_manager",
level=NotificationLevel.ERROR
)
handler.assert_any_call(
'Retrying to connect to server TestConnector',
'OPC_CONNECTION_RETRY__TestConnector',
NotificationLevel.ERROR
assert result is False
def test_check_opc_listenning_error_notification_and_retry(opc_manager):
opc_manager.non_receive_count = 14
opc_manager.notification_handler.build_and_send_notification = MagicMock()
result = opc_manager.check_opc_listenning()
assert opc_manager.non_receive_count == 15
# Should be called twice: once for 5, once for 15
assert opc_manager.notification_handler.build_and_send_notification.call_count == 2
calls = opc_manager.notification_handler.build_and_send_notification.call_args_list
# First call: 5 cycles warning
assert calls[0].kwargs == dict(
notification_id=f'OPC_LISTENNING_STOPPED__{opc_manager.name}',
message=f'15 cycles without receive from OPC {opc_manager.name}. Tags: {json.dumps(opc_manager.nodes)}',
block="opc_manager",
level=NotificationLevel.ERROR
)
opc_manager_subscribed.init_collector.assert_called_once_with(
tags, 1000, 500)
# Second call: 15 cycles retry
assert calls[1].kwargs == dict(
notification_id=f'OPC_CONNECTION_RETRY__{opc_manager.name}',
message=f'Retrying to connect to server {opc_manager.name}',
block="opc_manager",
level=NotificationLevel.ERROR
)
assert result is True