SIENTIAPDE-1646
SIENTIAPDE-1646 Update dependencies and refactor OPC integration - Replaced `opcua` with `asyncua` in `requirements-local.txt` and `requirements.txt` to utilize the async capabilities. - Updated `values.yaml` to change the GitHub branch from `feature/SIENTIAPDE-1646` to `release/SIENTIAPDE-1646`. - Refactored `opc.py` and `opc_repository.py` to accommodate the new `asyncua` library, ensuring compatibility with the synchronous API. - Adjusted tests in `test_opc_repository.py` to reflect changes in the client implementation and maintain functionality.
This commit is contained in:
@@ -143,7 +143,7 @@ class OPC(SientiaMonitoring):
|
|||||||
level=NotificationLevel.ERROR,
|
level=NotificationLevel.ERROR,
|
||||||
attachment_content=trace,
|
attachment_content=trace,
|
||||||
)
|
)
|
||||||
raise e
|
raise
|
||||||
|
|
||||||
def validate_server(self, server_id: str, metadata: dict[str, Any]) -> bool:
|
def validate_server(self, server_id: str, metadata: dict[str, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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,
|
``asyncua.sync.Client`` runs the asyncio client on a background thread so callers
|
||||||
and writes node values with typed variants and Prometheus-compatible metrics.
|
stay synchronous. Connect/disconnect, optional Basic256 security, session checks,
|
||||||
|
and typed writes mirror the previous python-opcua integration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -12,8 +13,9 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from opcua import Client, ua
|
from asyncua import ua
|
||||||
from opcua.crypto import security_policies
|
from asyncua.crypto import security_policies
|
||||||
|
from asyncua.sync import Client
|
||||||
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
||||||
from sientia_do.notifications.models import NotificationLevel
|
from sientia_do.notifications.models import NotificationLevel
|
||||||
from sientia_do.observability.logger import Logger
|
from sientia_do.observability.logger import Logger
|
||||||
@@ -54,7 +56,7 @@ class OpcRepository(SientiaMonitoring):
|
|||||||
url: OPC UA endpoint URL.
|
url: OPC UA endpoint URL.
|
||||||
id: Server identifier used in metrics and notifications.
|
id: Server identifier used in metrics and notifications.
|
||||||
server_name: Human-readable server name for labels.
|
server_name: Human-readable server name for labels.
|
||||||
client: Active opcua.Client instance while connected.
|
client: Active ``asyncua.sync.Client`` while connected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -115,16 +117,17 @@ class OpcRepository(SientiaMonitoring):
|
|||||||
if self.client is None:
|
if self.client is None:
|
||||||
raise ValueError('Client must be initialized before setting security')
|
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.logger.custom_info('Setting security...', self.metadata)
|
||||||
self.client.set_security(
|
self.client.set_security(
|
||||||
security_policies.SecurityPolicyBasic256,
|
security_policies.SecurityPolicyBasic256,
|
||||||
str(cert),
|
str(cert),
|
||||||
str(private_key),
|
str(private_key),
|
||||||
|
None,
|
||||||
str(server_cert) if server_cert else None,
|
str(server_cert) if server_cert else None,
|
||||||
)
|
)
|
||||||
self.client.secure_channel_timeout = 10000000
|
self.client.aio_obj.secure_channel_timeout = 10000000
|
||||||
self.client.session_timeout = 10000000
|
self.client.aio_obj.session_timeout = 10000000
|
||||||
|
|
||||||
def connect(self) -> tuple[bool, dict[str, Any]]:
|
def connect(self) -> tuple[bool, dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
@@ -136,11 +139,11 @@ class OpcRepository(SientiaMonitoring):
|
|||||||
|
|
||||||
self.client = Client(self.url, timeout=10)
|
self.client = Client(self.url, timeout=10)
|
||||||
|
|
||||||
self.client.name = self.pod_id
|
self.client.aio_obj.name = self.pod_id
|
||||||
self.client.application_name = self.pod_id
|
self.client.aio_obj.description = self.pod_id
|
||||||
pod_uri = self.pod_id.replace('-', ':')
|
pod_uri = self.pod_id.replace('-', ':')
|
||||||
self.client.application_uri = pod_uri
|
self.client.application_uri = pod_uri
|
||||||
self.client.product_uri = pod_uri
|
self.client.aio_obj.product_uri = pod_uri
|
||||||
|
|
||||||
if self.cert_path:
|
if self.cert_path:
|
||||||
self.set_security()
|
self.set_security()
|
||||||
@@ -336,7 +339,7 @@ class OpcRepository(SientiaMonitoring):
|
|||||||
Write a typed value to an OPC UA node after validating connectivity.
|
Write a typed value to an OPC UA node after validating connectivity.
|
||||||
|
|
||||||
Args:
|
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.
|
value: Scalar value to encode.
|
||||||
data_type: Key into ``data_type_map`` (e.g. float, str).
|
data_type: Key into ``data_type_map`` (e.g. float, str).
|
||||||
logger: Caller logger for per-write traces.
|
logger: Caller logger for per-write traces.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
temporalio
|
temporalio
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
opcua
|
asyncua==1.0.6
|
||||||
redis
|
redis
|
||||||
#git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.0
|
#git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.0
|
||||||
/home/grezewave/Documents/projects/sientia/sientia-dataops-library
|
/home/grezewave/Documents/projects/sientia/sientia-dataops-library
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
temporalio
|
temporalio
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
opcua
|
asyncua==1.0.6
|
||||||
redis
|
redis
|
||||||
sientia_do==1.12.0
|
sientia_do==1.12.0
|
||||||
sientia_model==0.8.2
|
sientia_model==0.8.2
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from datetime import datetime
|
|||||||
from unittest.mock import ANY, MagicMock, Mock, patch
|
from unittest.mock import ANY, MagicMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from opcua.crypto import security_policies
|
from asyncua.crypto import security_policies
|
||||||
from sientia_do.notifications.models import NotificationLevel
|
from sientia_do.notifications.models import NotificationLevel
|
||||||
|
|
||||||
from laborious.utils.repository.opc_repository import OpcRepository
|
from laborious.utils.repository.opc_repository import OpcRepository
|
||||||
@@ -39,6 +39,7 @@ def opc_repository(mock_logger):
|
|||||||
def mock_client():
|
def mock_client():
|
||||||
with patch('laborious.utils.repository.opc_repository.Client') as mock:
|
with patch('laborious.utils.repository.opc_repository.Client') as mock:
|
||||||
client_instance = MagicMock()
|
client_instance = MagicMock()
|
||||||
|
client_instance.aio_obj = MagicMock()
|
||||||
mock.return_value = client_instance
|
mock.return_value = client_instance
|
||||||
yield client_instance
|
yield client_instance
|
||||||
|
|
||||||
@@ -75,10 +76,11 @@ def test_set_security(opc_repository, mock_client):
|
|||||||
security_policies.SecurityPolicyBasic256,
|
security_policies.SecurityPolicyBasic256,
|
||||||
'/path/to/cert.pem',
|
'/path/to/cert.pem',
|
||||||
'/path/to/key.pem',
|
'/path/to/key.pem',
|
||||||
|
None,
|
||||||
'/path/to/server_cert.pem',
|
'/path/to/server_cert.pem',
|
||||||
)
|
)
|
||||||
assert mock_client.secure_channel_timeout == 10000000
|
assert mock_client.aio_obj.secure_channel_timeout == 10000000
|
||||||
assert mock_client.session_timeout == 10000000
|
assert mock_client.aio_obj.session_timeout == 10000000
|
||||||
|
|
||||||
|
|
||||||
def test_set_security_missing_certificates(opc_repository):
|
def test_set_security_missing_certificates(opc_repository):
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ global:
|
|||||||
- name: GITHUB_REPO_URL
|
- name: GITHUB_REPO_URL
|
||||||
value: "git@github.com:Aignosi/sientia-dataops-laborious_temporal.git"
|
value: "git@github.com:Aignosi/sientia-dataops-laborious_temporal.git"
|
||||||
- name: GITHUB_BRANCH
|
- name: GITHUB_BRANCH
|
||||||
value: "feature/SIENTIAPDE-1646"
|
value: "release/SIENTIAPDE-1646"
|
||||||
- name: PYTHON_APP
|
- name: PYTHON_APP
|
||||||
value: "laborious.worker.worker"
|
value: "laborious.worker.worker"
|
||||||
- name: PYPI_SERVER
|
- name: PYPI_SERVER
|
||||||
@@ -231,7 +231,7 @@ global:
|
|||||||
# - The runtime name is used by the worker bootstrap to resolve plugins and task queues.
|
# - 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.
|
# - Keep runtime names in sync with the plugin-store runtime names.
|
||||||
runtimes:
|
runtimes:
|
||||||
- name: "single"
|
- name: "basic"
|
||||||
replicas: 1
|
replicas: 1
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user