SIENTIAPDE-1973

SIENTIAPDE-1973: Implement per-connector OPC client certificates and remove legacy read engine.
This update overhauls certificate management, transitioning from a single platform certificate to individual client certificates defined in the slot configuration. The CertProvider now downloads certificates from MinIO based on certificate_id, applying dynamic security policies (mode, policy) from the slot. The legacy READ_ENGINE=legacy option and OpcManager have been entirely removed, standardizing on the ServerReader with strict slot v2 enforcement. Write command resolution (ServerConfigResolver) now consistently uses connectors joined with opc_certificates. The CERT_BUCKET environment variable is no longer used.
This commit is contained in:
2026-10-29 12:39:21 -03:00
parent d68e842e6e
commit ecb055eefb
32 changed files with 1639 additions and 2284 deletions

View File

@@ -3,7 +3,7 @@
Populates:
1. Mongo `connectors` + `connector_tags` (each tag with `metadata: {}` — required
for documentation, per the feature owner).
2. Redis slot v1 (`slot:opc_tags:1`) so the legacy subscription path collects them.
2. Redis slot v2 (`slot:opc_tags:1`, contract C1) so the subscription path collects them.
3. Postgres test predictions table (with the `write_status` column) + one row the
NATS write test can UPDATE (correlated by timestamp + model_id).
@@ -37,41 +37,53 @@ except ImportError:
OPC_URL = os.getenv('OPC_SERVER_URL', 'opc.tcp://localhost:49320')
# No client certificate: the gateway connects without security (SIENTIAPDE-1974 D5).
# To test security, point client_certificate_id at an active client certificate of
# opc_certificates and set mode/policy (e.g. SignAndEncrypt / Basic256Sha256).
SECURITY = {'mode': 'None', 'policy': 'None', 'client_certificate_id': None}
CONNECTOR = {
'connector_id': 9001,
'connector_id': 'e2e-kepserver',
'type': 'opc_ua',
'name': 'e2e-kepserver',
'url': OPC_URL,
'server_uri': 'urn:e2e:gateway:test',
'subscription_period_ms': 1000,
'use_security': False, # round 1; flip after certs are in sientia-dataops
'security': SECURITY,
'status': 'Active',
'description': 'OPC Gateway e2e validation connector (SIENTIAPDE-1972)',
}
# metadata: {} on every tag — feature owner asked test tags to carry the field.
TAGS = [
{'tag_id': 9101, 'connector_id': 9001, 'address': 'ns=2;s=Simulation Examples.Functions.Ramp1',
{'tag_id': 9101, 'connector_id': 'e2e-kepserver', 'address': 'ns=2;s=Simulation Examples.Functions.Ramp1',
'tag_name': 'e2e_ramp', 'category': 'Read', 'data_type': 'float', 'unit': '-',
'status': 'Active', 'description': 'e2e read tag', 'metadata': {}},
{'tag_id': 9102, 'connector_id': 9001, 'address': 'ns=2;s=Simulation Examples.Functions.User1',
{'tag_id': 9102, 'connector_id': 'e2e-kepserver', 'address': 'ns=2;s=Simulation Examples.Functions.User1',
'tag_name': 'e2e_write_target', 'category': 'Write', 'data_type': 'float', 'unit': '-',
'status': 'Active', 'description': 'e2e write tag', 'metadata': {}},
]
# Slot v1 (legacy path the gateway still runs until SIENTIAPDE-1974 delivers slot v2).
SLOT_V1 = {
# Slot v2 (contract C1), as the Orchestrator writes it for the connector above.
SLOT = {
'e2e-kepserver': {
'name': 'e2e-kepserver',
'server_id': '9001',
'server_id': 'e2e-kepserver',
'url': OPC_URL,
'server_uri': 'urn:e2e:gateway:test',
'subscription_period_ms': 1000,
'security': {'mode': SECURITY['mode'], 'policy': SECURITY['policy']},
'certificate': None,
'tags': {
TAGS[0]['address']: {
'tag_name': TAGS[0]['tag_name'],
'frequency': '5000',
'topics': ['raw_e2e-gateway-test'],
'frequency': 5000,
'active_read': {'enabled': False},
'names': [
{
'collection': 'raw_e2e-gateway-test',
'tag_name': TAGS[0]['tag_name'],
'model_id': '9001',
'model_name': 'e2e-model',
}
],
}
},
}
@@ -94,12 +106,11 @@ ON CONFLICT DO NOTHING;
# The matching NATS write command for the row above (paste into `nats pub opc.write '...'`).
NATS_CMD = {
'msg_id': 'e2e-0001', 'server_id': '9001', 'addr': TAGS[1]['address'],
'msg_id': 'e2e-0001', 'server_id': 'e2e-kepserver', 'addr': TAGS[1]['address'],
'value': 42.5, 'data_type': 'float', 'tag_type': 'prediction',
'model_id': '9001', 'model_name': 'e2e-model',
'schema': 'sientia_data', 'table_name': 'e2e_predictions',
'timestamp': '2026-07-15T12:00:00Z',
'url': OPC_URL, 'use_security': False, 'server_uri': 'urn:e2e:gateway:test',
}
@@ -119,8 +130,8 @@ def seed_redis():
import redis
r = redis.Redis(host=os.getenv('REDIS_HOST', 'localhost'), port=int(os.getenv('REDIS_PORT', '6379')))
r.set('slot:opc_tags:1', json.dumps(SLOT_V1))
print('redis: wrote slot:opc_tags:1 (v1)')
r.set('slot:opc_tags:1', json.dumps(SLOT))
print('redis: wrote slot:opc_tags:1 (v2)')
def seed_postgres():
@@ -144,7 +155,7 @@ def main() -> int:
if args.print_only:
print('connector:', json.dumps(CONNECTOR, indent=2))
print('tags:', json.dumps(TAGS, indent=2))
print('slot v1:', json.dumps(SLOT_V1, indent=2))
print('slot v2:', json.dumps(SLOT, indent=2))
print('sql:', PREDICTIONS_SQL)
print('nats write cmd:', json.dumps(NATS_CMD, indent=2))
return 0