The rc3 OpcManager is restored unchanged (with its tests and scripts/generate-and-push-opc-cert.sh) for validation and as a fallback until the end of SIENTIAPDE-1974; it is removed only once it is unused and the test suite passes the same without it. - READ_ENGINE=v2 (default): ServerReader/SubscriptionEngine over the shared pool, certificate from MinIO. READ_ENGINE=legacy: the rc3 OpcManager with its own connection, slot v1 only (a v2 slot is rejected and notified) and the certificate files of the Gitea clone. An invalid value falls back to v2 with a warning. Writes/gRPC always use the pool. - pool: OPC clients carry the pod id as application/session name and product URI (as OpcManager did), so each pod's session is recognizable in the server console; application_uri stays the server_uri (certificate SAN). - README/CLAUDE.md document the plan B and how to remove it. Tests: 275 unit tests pass (restored OpcManager tests unchanged); ruff clean. Local e2e: both engines read the same slot v1 from an asyncua server and write identical documents to the same collections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
628 lines
20 KiB
Python
628 lines
20 KiB
Python
import json
|
|
from datetime import datetime
|
|
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
|
|
|
|
import pytest
|
|
from asyncua.crypto.security_policies import SecurityPolicyBasic256
|
|
from pytest import fixture, mark
|
|
from sientia_do.notifications.models import NotificationLevel
|
|
|
|
from ingestor.managers.opc_manager import OpcManager
|
|
|
|
tags = {
|
|
'ns=3;i=1001': {
|
|
'aggregation_function': 'LTS',
|
|
'frequency': 1000,
|
|
'max_value': 100,
|
|
'min_value': 0,
|
|
'tag_name': 'Counter',
|
|
},
|
|
'ns=3;i=1003': {
|
|
'aggregation_function': 'AVG',
|
|
'frequency': 1000,
|
|
'max_value': 100,
|
|
'min_value': 0,
|
|
'tag_name': 'Random',
|
|
},
|
|
'ns=3;i=1004': {
|
|
'aggregation_function': 'MDN',
|
|
'frequency': 1000,
|
|
'max_value': 100,
|
|
'min_value': 0,
|
|
'tag_name': 'Sawtooth',
|
|
},
|
|
}
|
|
|
|
metadata = {
|
|
'metadata': {
|
|
'model_id': 'test_model',
|
|
'model_name': 'test_model',
|
|
'workflow_name': 'test_workflow',
|
|
'schema_name': 'test_schedule',
|
|
},
|
|
}
|
|
|
|
|
|
@fixture
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
def raw_opc_manager(mock_metrics):
|
|
opc_manager = OpcManager(
|
|
name='TestConnector',
|
|
url='opc.tcp://localhost:4840',
|
|
data_manager=AsyncMock(),
|
|
subscription_period_ms=1000,
|
|
logger=MagicMock(),
|
|
server_uri='opc.tcp://localhost:4840',
|
|
notification_handler=MagicMock(),
|
|
metadata=metadata['metadata'],
|
|
metrics_controller=AsyncMock(),
|
|
)
|
|
|
|
opc_manager.emit_metric = AsyncMock()
|
|
opc_manager.send_notification_async = AsyncMock()
|
|
opc_manager.send_notification = MagicMock()
|
|
|
|
return opc_manager
|
|
|
|
|
|
@fixture
|
|
def opc_manager(raw_opc_manager):
|
|
raw_opc_manager.client = AsyncMock()
|
|
raw_opc_manager.cert_path = 'cert.pem'
|
|
raw_opc_manager.private_key_path = 'private_key.pem'
|
|
raw_opc_manager.server_cert_path = 'server_cert.pem'
|
|
|
|
return raw_opc_manager
|
|
|
|
|
|
@fixture
|
|
def opc_manager_subscribed(opc_manager):
|
|
opc_manager.subscriptions['sub1'] = AsyncMock()
|
|
|
|
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={}'
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_shutdown_success(opc_manager):
|
|
opc_manager.disconnect = AsyncMock()
|
|
|
|
await opc_manager.shutdown()
|
|
|
|
opc_manager.disconnect.assert_called_once()
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_shutdown_error(opc_manager):
|
|
opc_manager.disconnect = AsyncMock(side_effect=Exception('Test error'))
|
|
|
|
await opc_manager.shutdown()
|
|
|
|
opc_manager.logger.error.assert_called_once_with('Error during cleanup: Test error')
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_set_security_success(opc_manager):
|
|
await opc_manager.set_security()
|
|
|
|
assert opc_manager.client.application_uri == opc_manager.server_uri
|
|
|
|
opc_manager.client.set_security.assert_called_once_with(
|
|
SecurityPolicyBasic256,
|
|
certificate=opc_manager.cert_path,
|
|
private_key=opc_manager.private_key_path,
|
|
server_certificate=opc_manager.server_cert_path,
|
|
)
|
|
|
|
assert opc_manager.client.secure_channel_timeout == 10000000
|
|
assert opc_manager.client.session_timeout == 10000000
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_set_security_no_cert(opc_manager):
|
|
opc_manager.cert_path = None
|
|
opc_manager.private_key_path = None
|
|
|
|
try:
|
|
await opc_manager.set_security()
|
|
except ValueError as e:
|
|
assert str(e) == 'Certificate and private key paths must be provided for secure connection.'
|
|
else:
|
|
raise AssertionError('ValueError not raised')
|
|
|
|
assert opc_manager.client.set_security.call_count == 0
|
|
|
|
|
|
@mark.asyncio
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@patch('ingestor.managers.opc_manager.Client')
|
|
async def test_connect_no_security(client, mock_metrics, raw_opc_manager):
|
|
raw_opc_manager.set_security = AsyncMock()
|
|
client.return_value = AsyncMock()
|
|
|
|
await raw_opc_manager.connect()
|
|
|
|
client.assert_called_once_with(raw_opc_manager.url, timeout=10, watchdog_intervall=3600000)
|
|
raw_opc_manager.client.connect.assert_called_once()
|
|
raw_opc_manager.set_security.assert_not_called()
|
|
raw_opc_manager.emit_metric.assert_has_calls(
|
|
[
|
|
call(
|
|
metric_object=mock_metrics.OPC_CONNECTIONS_TOTAL,
|
|
tags={
|
|
'pod_id': raw_opc_manager.pod_id,
|
|
'server_name': raw_opc_manager.name,
|
|
},
|
|
),
|
|
call(
|
|
metric_object=mock_metrics.OPC_CONNECTION_STATUS,
|
|
method='set',
|
|
value=1,
|
|
tags={
|
|
'pod_id': raw_opc_manager.pod_id,
|
|
'server_name': raw_opc_manager.name,
|
|
'server_url': raw_opc_manager.url,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
@patch('ingestor.managers.opc_manager.Client')
|
|
async def test_connect_with_security(client, raw_opc_manager):
|
|
raw_opc_manager.cert_path = 'cert.pem'
|
|
raw_opc_manager.private_key_path = 'private_key.pem'
|
|
raw_opc_manager.server_cert_path = 'server_cert.pem'
|
|
raw_opc_manager.set_security = AsyncMock()
|
|
client.return_value = AsyncMock()
|
|
|
|
await raw_opc_manager.connect()
|
|
|
|
client.assert_called_once_with(raw_opc_manager.url, timeout=10, watchdog_intervall=3600000)
|
|
raw_opc_manager.client.connect.assert_called_once()
|
|
raw_opc_manager.set_security.assert_called_once()
|
|
|
|
|
|
@mark.asyncio
|
|
@patch('ingestor.managers.opc_manager.Client')
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
async def test_connect_exception_handling_and_metrics(
|
|
mock_metrics_module, mock_opc_client_class, raw_opc_manager
|
|
):
|
|
mock_client_instance = mock_opc_client_class.return_value
|
|
simulated_error_message = 'Erro de conexão simulado'
|
|
mock_client_instance.connect.side_effect = Exception(simulated_error_message)
|
|
|
|
raw_opc_manager.disconnect = AsyncMock()
|
|
|
|
opc_manager_instance = raw_opc_manager
|
|
opc_manager_instance.cert_path = None
|
|
|
|
with pytest.raises(Exception, match=simulated_error_message):
|
|
await opc_manager_instance.connect()
|
|
|
|
opc_manager_instance.disconnect.assert_called_once()
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_create_subscription_no_client(raw_opc_manager):
|
|
try:
|
|
await raw_opc_manager.create_subscription('sub1')
|
|
except ValueError as e:
|
|
assert str(e) == 'Client not connected. Call connect first.'
|
|
else:
|
|
raise AssertionError('ValueError not raised')
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_create_subscription_success_has_period(opc_manager):
|
|
await opc_manager.create_subscription('sub1')
|
|
|
|
opc_manager.client.create_subscription.assert_called_once_with(
|
|
opc_manager.subscription_period_ms, opc_manager
|
|
)
|
|
assert opc_manager.subscriptions['sub1'] is not None
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_create_subscription_success_no_period(opc_manager):
|
|
await opc_manager.create_subscription('sub1')
|
|
|
|
opc_manager.client.create_subscription.assert_called_once_with(
|
|
opc_manager.subscription_period_ms, opc_manager
|
|
)
|
|
assert opc_manager.subscriptions['sub1'] is not None
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_create_subscription_with_metrics(metrics, opc_manager):
|
|
await opc_manager.create_subscription('sub1')
|
|
|
|
opc_manager.emit_metric.assert_called_once_with(
|
|
metric_object=metrics.OPC_SUBSCRIPTIONS_CREATED,
|
|
method='inc',
|
|
value=1,
|
|
tags={
|
|
'pod_id': opc_manager.pod_id,
|
|
'server_name': opc_manager.name,
|
|
'slot_name': 'sub1',
|
|
},
|
|
)
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_create_subscription_exception_during_client_call(
|
|
mock_metrics_module, raw_opc_manager
|
|
):
|
|
opc_manager_instance = raw_opc_manager
|
|
opc_manager_instance.client = AsyncMock()
|
|
|
|
subscription_name = 'test_sub_client_error'
|
|
simulated_error_message = 'Falha ao criar subscrição no cliente OPC'
|
|
|
|
opc_manager_instance.client.create_subscription.side_effect = Exception(simulated_error_message)
|
|
|
|
with pytest.raises(Exception, match=simulated_error_message):
|
|
await opc_manager_instance.create_subscription(subscription_name)
|
|
|
|
opc_manager_instance.client.create_subscription.assert_called_once_with(
|
|
opc_manager_instance.subscription_period_ms, opc_manager_instance
|
|
)
|
|
|
|
opc_manager_instance.logger.error.assert_called_once_with(
|
|
f'Failed to create subscription {subscription_name} on {opc_manager_instance.name}: {simulated_error_message}'
|
|
)
|
|
|
|
mock_metrics_module.OPC_SUBSCRIPTIONS_CREATED.labels.assert_not_called()
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_subscribe_no_subscription(metrics, opc_manager):
|
|
try:
|
|
await opc_manager.subscribe('sub1', tags, 1000)
|
|
except ValueError as e:
|
|
assert str(e) == 'Subscription not created. Call create_subscription first.'
|
|
else:
|
|
raise AssertionError('ValueError not raised')
|
|
metrics.OPC_TAGS_SUBSCRIBED.labels.assert_not_called()
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_subscribe_success(opc_manager_subscribed):
|
|
opc_manager_subscribed.client.get_node = MagicMock()
|
|
opc_manager_subscribed.nodes = {'ns=3;i=1001': 'data'}
|
|
|
|
await opc_manager_subscribed.subscribe('sub1', tags, 1000)
|
|
|
|
assert opc_manager_subscribed.nodes == tags
|
|
opc_manager_subscribed.subscriptions['sub1'].subscribe_data_change.assert_called_once_with(
|
|
[opc_manager_subscribed.client.get_node(n) for n in tags]
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_unsubscribe_no_subscription(opc_manager):
|
|
await 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
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_unsubscribe_success(opc_manager_subscribed):
|
|
await opc_manager_subscribed.unsubscribe('sub1')
|
|
|
|
assert opc_manager_subscribed.subscriptions.get('sub1') is None
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_disconnection_fallback_success(opc_manager):
|
|
opc_manager.client = AsyncMock()
|
|
opc_manager.client.disconnect.return_value = True
|
|
result = await opc_manager.disconnection_fallback()
|
|
assert result == []
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_disconnection_fallback_fail(opc_manager):
|
|
opc_manager.client = AsyncMock()
|
|
opc_manager.client.disconnect.side_effect = Exception('Test error')
|
|
result = await opc_manager.disconnection_fallback()
|
|
assert result == [
|
|
{'attempt': 1, 'error': 'Test error', 'traceback': ANY},
|
|
{'attempt': 2, 'error': 'Test error', 'traceback': ANY},
|
|
{'attempt': 3, 'error': 'Test error', 'traceback': ANY},
|
|
{'attempt': 4, 'error': 'Test error', 'traceback': ANY},
|
|
{'attempt': 5, 'error': 'Test error', 'traceback': ANY},
|
|
]
|
|
assert opc_manager.client.disconnect.call_count == 5
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_disconnect_success(opc_manager_subscribed):
|
|
opc_manager_subscribed.client = MagicMock()
|
|
|
|
await opc_manager_subscribed.disconnect()
|
|
|
|
opc_manager_subscribed.subscriptions['sub1'].delete.assert_called_once()
|
|
assert opc_manager_subscribed.client is None
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_disconnect_no_client(opc_manager_subscribed):
|
|
opc_manager_subscribed.client = None
|
|
assert await opc_manager_subscribed.disconnect() is None
|
|
|
|
opc_manager_subscribed.logger.warning.assert_has_calls(
|
|
[
|
|
call('Client already disconnected.'),
|
|
]
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_disconnect_error_unsubscribe(opc_manager_subscribed):
|
|
opc_manager_subscribed.client = MagicMock(disconnect=AsyncMock())
|
|
opc_manager_subscribed.subscriptions['sub1'] = MagicMock(
|
|
delete=AsyncMock(side_effect=Exception('Test error'))
|
|
)
|
|
|
|
await opc_manager_subscribed.disconnect()
|
|
|
|
opc_manager_subscribed.subscriptions['sub1'].delete.assert_called_once()
|
|
opc_manager_subscribed.client = None
|
|
opc_manager_subscribed.logger.error.assert_called_once_with(
|
|
'Failed to clean up subscription: Test error'
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_disconnect_error(opc_manager_subscribed):
|
|
opc_manager_subscribed.client = MagicMock()
|
|
opc_manager_subscribed.client.disconnect = MagicMock(side_effect=Exception('Test error'))
|
|
|
|
await opc_manager_subscribed.disconnect()
|
|
|
|
opc_manager_subscribed.subscriptions['sub1'].delete.assert_called_once()
|
|
opc_manager_subscribed.client = None
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_disconnect_metrics_on_successful_path(mock_metrics_module, raw_opc_manager):
|
|
mock_metrics_module.OPC_CONNECTION_STATUS.reset_mock()
|
|
mock_metrics_module.OPC_TAGS_SUBSCRIBED.reset_mock()
|
|
|
|
raw_opc_manager.client = MagicMock()
|
|
mock_sub1 = MagicMock()
|
|
mock_sub2 = MagicMock()
|
|
raw_opc_manager.subscriptions = {'sub1': mock_sub1, 'sub2': mock_sub2}
|
|
|
|
await raw_opc_manager.disconnect()
|
|
|
|
raw_opc_manager.emit_metric.assert_has_calls(
|
|
[
|
|
call(
|
|
metric_object=mock_metrics_module.OPC_CONNECTION_STATUS,
|
|
method='set',
|
|
value=0,
|
|
tags={
|
|
'pod_id': raw_opc_manager.pod_id,
|
|
'server_name': raw_opc_manager.name,
|
|
'server_url': raw_opc_manager.url,
|
|
},
|
|
),
|
|
call(
|
|
metric_object=mock_metrics_module.OPC_TAGS_SUBSCRIBED,
|
|
method='set',
|
|
value=0,
|
|
tags={
|
|
'pod_id': raw_opc_manager.pod_id,
|
|
'server_name': raw_opc_manager.name,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_datachange_notification(metrics, opc_manager_subscribed):
|
|
data = MagicMock(
|
|
monitored_item=MagicMock(
|
|
Value=MagicMock(
|
|
Value=MagicMock(Value=42),
|
|
SourceTimestamp=datetime.strptime('2021-01-01T00:00:00', '%Y-%m-%dT%H:%M:%S'),
|
|
)
|
|
)
|
|
)
|
|
opc_manager_subscribed.nodes = {
|
|
'ns=3;i=1001': {
|
|
'tag_name': 'Counter',
|
|
'cycle_rule': {'cycle_increment': 1.0, 'cycle_count': 2},
|
|
'topics': ['topic1', 'topic2'],
|
|
}
|
|
}
|
|
|
|
await opc_manager_subscribed.datachange_notification('ns=3;i=1001', None, data)
|
|
|
|
opc_manager_subscribed.data_manager.publish.assert_any_call(
|
|
'topic1',
|
|
{
|
|
'tag': 'ns=3;i=1001',
|
|
'name': 'Counter',
|
|
'timestamp': '2021-01-01 00:00:00-0300',
|
|
'value': 42,
|
|
},
|
|
)
|
|
opc_manager_subscribed.data_manager.publish.assert_any_call(
|
|
'topic2',
|
|
{
|
|
'tag': 'ns=3;i=1001',
|
|
'name': 'Counter',
|
|
'timestamp': '2021-01-01 00:00:00-0300',
|
|
'value': 42,
|
|
},
|
|
)
|
|
assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 0
|
|
|
|
opc_manager_subscribed.emit_metric.assert_called_once_with(
|
|
metric_object=metrics.OPC_CYCLES_WITHOUT_DATA,
|
|
method='set',
|
|
value=0,
|
|
tags={
|
|
'pod_id': opc_manager_subscribed.pod_id,
|
|
'server_name': opc_manager_subscribed.name,
|
|
},
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
async 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},
|
|
}
|
|
}
|
|
|
|
await opc_manager.check_cycles()
|
|
|
|
# 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.send_notification_async.assert_not_called()
|
|
|
|
|
|
@mark.asyncio
|
|
async 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()
|
|
|
|
await opc_manager.check_cycles()
|
|
|
|
# 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.send_notification_async.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,
|
|
metadata=metadata['metadata'],
|
|
)
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_check_opc_listenning_no_notification(metrics, opc_manager):
|
|
opc_manager.non_receive_count = 3
|
|
|
|
result = await opc_manager.check_opc_listenning()
|
|
|
|
assert opc_manager.non_receive_count == 4
|
|
opc_manager.send_notification_async.assert_not_called()
|
|
assert result is False
|
|
|
|
opc_manager.emit_metric.assert_called_once_with(
|
|
metric_object=metrics.OPC_CYCLES_WITHOUT_DATA,
|
|
method='set',
|
|
value=opc_manager.non_receive_count,
|
|
tags={
|
|
'pod_id': opc_manager.pod_id,
|
|
'server_name': opc_manager.name,
|
|
},
|
|
)
|
|
|
|
|
|
@mark.asyncio
|
|
async def test_check_opc_listenning_warning_notification(opc_manager):
|
|
opc_manager.non_receive_count = 4
|
|
|
|
result = await opc_manager.check_opc_listenning()
|
|
|
|
assert opc_manager.non_receive_count == 5
|
|
opc_manager.send_notification_async.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,
|
|
metadata=metadata['metadata'],
|
|
)
|
|
assert result is False
|
|
|
|
|
|
@patch('ingestor.managers.opc_manager.metrics')
|
|
@mark.asyncio
|
|
async def test_check_opc_listenning_error_notification_and_retry(metrics, opc_manager):
|
|
opc_manager.non_receive_count = 14
|
|
|
|
result = await 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.send_notification_async.call_count == 2
|
|
calls = opc_manager.send_notification_async.call_args_list
|
|
# First call: 5 cycles warning
|
|
assert calls[0].kwargs == {
|
|
'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,
|
|
'metadata': metadata['metadata'],
|
|
}
|
|
# Second call: 15 cycles retry
|
|
assert calls[1].kwargs == {
|
|
'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,
|
|
'metadata': metadata['metadata'],
|
|
}
|
|
assert result is True
|
|
|
|
opc_manager.emit_metric.assert_has_calls(
|
|
[
|
|
call(
|
|
metric_object=metrics.OPC_CYCLES_WITHOUT_DATA,
|
|
method='set',
|
|
value=opc_manager.non_receive_count,
|
|
tags={
|
|
'pod_id': opc_manager.pod_id,
|
|
'server_name': opc_manager.name,
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
opc_manager.emit_metric.assert_has_calls(
|
|
[
|
|
call(
|
|
metric_object=metrics.OPC_RECONNECTIONS_TOTAL,
|
|
tags={
|
|
'pod_id': opc_manager.pod_id,
|
|
'server_name': opc_manager.name,
|
|
},
|
|
),
|
|
]
|
|
)
|