diff --git a/laborious/activities/opc.py b/laborious/activities/opc.py index 277d935..ef1249f 100644 --- a/laborious/activities/opc.py +++ b/laborious/activities/opc.py @@ -143,7 +143,7 @@ class OPC(SientiaMonitoring): level=NotificationLevel.ERROR, attachment_content=trace, ) - raise e + raise def validate_server(self, server_id: str, metadata: dict[str, Any]) -> bool: """ diff --git a/laborious/utils/repository/opc_repository.py b/laborious/utils/repository/opc_repository.py index 2e797f3..e675415 100644 --- a/laborious/utils/repository/opc_repository.py +++ b/laborious/utils/repository/opc_repository.py @@ -1,8 +1,9 @@ """ -Synchronous OPC UA client repository using python-opcua (opcua package). +Synchronous OPC UA client repository using asyncua (opcua-asyncio) ``sync`` API. -Connects to OPC UA servers, optionally configures Basic256 security, validates sessions, -and writes node values with typed variants and Prometheus-compatible metrics. +``asyncua.sync.Client`` runs the asyncio client on a background thread so callers +stay synchronous. Connect/disconnect, optional Basic256 security, session checks, +and typed writes mirror the previous python-opcua integration. """ import json @@ -12,8 +13,9 @@ from datetime import datetime from pathlib import Path from typing import Any -from opcua import Client, ua -from opcua.crypto import security_policies +from asyncua import ua +from asyncua.crypto import security_policies +from asyncua.sync import Client from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler from sientia_do.notifications.models import NotificationLevel from sientia_do.observability.logger import Logger @@ -54,7 +56,7 @@ class OpcRepository(SientiaMonitoring): url: OPC UA endpoint URL. id: Server identifier used in metrics and notifications. server_name: Human-readable server name for labels. - client: Active opcua.Client instance while connected. + client: Active ``asyncua.sync.Client`` while connected. """ def __init__( @@ -115,16 +117,17 @@ class OpcRepository(SientiaMonitoring): if self.client is None: raise ValueError('Client must be initialized before setting security') - self.client.application_uri = self.server_uri + self.client.application_uri = self.server_uri or self.client.application_uri self.logger.custom_info('Setting security...', self.metadata) self.client.set_security( security_policies.SecurityPolicyBasic256, str(cert), str(private_key), + None, str(server_cert) if server_cert else None, ) - self.client.secure_channel_timeout = 10000000 - self.client.session_timeout = 10000000 + self.client.aio_obj.secure_channel_timeout = 10000000 + self.client.aio_obj.session_timeout = 10000000 def connect(self) -> tuple[bool, dict[str, Any]]: """ @@ -136,11 +139,11 @@ class OpcRepository(SientiaMonitoring): self.client = Client(self.url, timeout=10) - self.client.name = self.pod_id - self.client.application_name = self.pod_id + self.client.aio_obj.name = self.pod_id + self.client.aio_obj.description = self.pod_id pod_uri = self.pod_id.replace('-', ':') self.client.application_uri = pod_uri - self.client.product_uri = pod_uri + self.client.aio_obj.product_uri = pod_uri if self.cert_path: self.set_security() @@ -336,7 +339,7 @@ class OpcRepository(SientiaMonitoring): Write a typed value to an OPC UA node after validating connectivity. Args: - node: Node id string accepted by opcua Client.get_node. + node: Node id string accepted by ``Client.get_node``. value: Scalar value to encode. data_type: Key into ``data_type_map`` (e.g. float, str). logger: Caller logger for per-write traces. diff --git a/requirements-local.txt b/requirements-local.txt index 8fbf9b2..df0d122 100644 --- a/requirements-local.txt +++ b/requirements-local.txt @@ -1,7 +1,7 @@ temporalio psycopg2-binary sqlalchemy -opcua +asyncua==1.0.6 redis #git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.0 /home/grezewave/Documents/projects/sientia/sientia-dataops-library diff --git a/requirements.txt b/requirements.txt index c848080..3b587fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ temporalio psycopg2-binary sqlalchemy -opcua +asyncua==1.0.6 redis sientia_do==1.12.0 sientia_model==0.8.2 diff --git a/tests/laborious/utils/repository/test_opc_repository.py b/tests/laborious/utils/repository/test_opc_repository.py index 88390fa..25ad5e9 100644 --- a/tests/laborious/utils/repository/test_opc_repository.py +++ b/tests/laborious/utils/repository/test_opc_repository.py @@ -3,7 +3,7 @@ from datetime import datetime from unittest.mock import ANY, MagicMock, Mock, patch import pytest -from opcua.crypto import security_policies +from asyncua.crypto import security_policies from sientia_do.notifications.models import NotificationLevel from laborious.utils.repository.opc_repository import OpcRepository @@ -39,6 +39,7 @@ def opc_repository(mock_logger): def mock_client(): with patch('laborious.utils.repository.opc_repository.Client') as mock: client_instance = MagicMock() + client_instance.aio_obj = MagicMock() mock.return_value = client_instance yield client_instance @@ -75,10 +76,11 @@ def test_set_security(opc_repository, mock_client): security_policies.SecurityPolicyBasic256, '/path/to/cert.pem', '/path/to/key.pem', + None, '/path/to/server_cert.pem', ) - assert mock_client.secure_channel_timeout == 10000000 - assert mock_client.session_timeout == 10000000 + assert mock_client.aio_obj.secure_channel_timeout == 10000000 + assert mock_client.aio_obj.session_timeout == 10000000 def test_set_security_missing_certificates(opc_repository): diff --git a/values.yaml b/values.yaml index 607d5ff..d0f7cb8 100644 --- a/values.yaml +++ b/values.yaml @@ -66,7 +66,7 @@ global: - name: GITHUB_REPO_URL value: "git@github.com:Aignosi/sientia-dataops-laborious_temporal.git" - name: GITHUB_BRANCH - value: "feature/SIENTIAPDE-1646" + value: "release/SIENTIAPDE-1646" - name: PYTHON_APP value: "laborious.worker.worker" - name: PYPI_SERVER @@ -231,7 +231,7 @@ global: # - The runtime name is used by the worker bootstrap to resolve plugins and task queues. # - Keep runtime names in sync with the plugin-store runtime names. runtimes: - - name: "single" + - name: "basic" replicas: 1 # -----------------------------------------------------------------------------