SIENTIAPDE-1974: fix(read): code-review fixes for the slot v2 read path
- config: GatewayConfig.certs_from_env() reads only CERT_BUCKET/CERT_CACHE_DIR; IngestorManager uses it, so a malformed write/gRPC env var (GRPC_PORT, WRITE_MIN_PAUSE_S, IDLE_CONN_MAX_S) can no longer stop the reads. - certs: CertProvider raises CertificateNotConfiguredError with an actionable message when no platform_certificates document exists; downloads once under an asyncio.Lock, caches the paths, writes atomically (tmp + os.replace); invalidate() forces a fresh download. - pool: server_id normalized to str (1 and '1' share a session); warns when a get() asks for other url/server_uri/use_security than the pooled client was opened with (client reused, the slot config wins); invalidates the cert cache when a secured connect fails. - SubscriptionEngine: tracks nodes per subscription and forgets them on unsubscribe (no more cycle alarms / tag count for removed tags), keeping addresses still monitored by another slot. - ServerReader.shutdown(evict) / IngestorManager.remove_server(evict): a server that only left this pod's slots keeps the shared connection for NATS writes/gRPC; lost servers and config changes still evict. - IngestorManager warns once per server when a slot still sets server_cert_path (server certificate pinning is no longer applied). - slots: CONNECTION_FIELDS exported and reused by ServerReader. Accepted, not changed: read metrics label server_name with the server_id (same as the write path, which only knows the id); a malformed slot is notified each lease attempt (alerts are deduplicated by notification_id). Tests: 236 unit tests pass (+18); ruff clean; local e2e against an asyncua server incl. slot moved (connection kept and reused by the write path) and lost-server eviction/re-initialization. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,8 +20,7 @@ import ingestor.metrics as metrics
|
||||
from ingestor.connection.pool import OpcConnectionPool
|
||||
from ingestor.engines.subscription import SubscriptionEngine
|
||||
from ingestor.io.data_sink import DataSink
|
||||
|
||||
_CONNECTION_FIELDS = ('url', 'server_uri', 'use_security', 'subscription_period_ms')
|
||||
from ingestor.io.slots import CONNECTION_FIELDS
|
||||
|
||||
|
||||
class ServerReader:
|
||||
@@ -42,7 +41,7 @@ class ServerReader:
|
||||
self.name = name
|
||||
self.server_id = str(server_config['server_id'])
|
||||
self.url = server_config['url']
|
||||
self.server_cfg = {k: server_config[k] for k in _CONNECTION_FIELDS if k in server_config}
|
||||
self.server_cfg = {k: server_config[k] for k in CONNECTION_FIELDS if k in server_config}
|
||||
self.pool = pool
|
||||
self.logger = logger
|
||||
self.config: dict | None = None
|
||||
@@ -77,22 +76,27 @@ class ServerReader:
|
||||
async def check_opc_listenning(self) -> bool:
|
||||
return await self.engine.check_opc_listenning(self.server_id)
|
||||
|
||||
async def shutdown(self):
|
||||
"""Drop every subscription and the pooled connection, so the next
|
||||
initialization reconnects (lost server or changed server config)."""
|
||||
async def shutdown(self, evict: bool = True):
|
||||
"""Drop every subscription of this server.
|
||||
|
||||
With `evict` (lost server or changed server config) the pooled connection is
|
||||
also dropped, so the next initialization reconnects. Without it (the slot just
|
||||
moved to another pod) the connection stays for writes/gRPC: the connection is
|
||||
shared, and the idle sweep closes it if nobody uses it anymore."""
|
||||
for slot in list(self.engine.subscriptions):
|
||||
await self.engine.unsubscribe(slot)
|
||||
await self.pool.evict(self.server_id)
|
||||
await self.engine.emit_metric(
|
||||
metric_object=metrics.OPC_CONNECTION_STATUS,
|
||||
method='set',
|
||||
value=0,
|
||||
tags={
|
||||
'pod_id': self.engine.pod_id,
|
||||
'server_name': self.server_id,
|
||||
'server_url': self.url,
|
||||
},
|
||||
)
|
||||
if evict:
|
||||
await self.pool.evict(self.server_id)
|
||||
await self.engine.emit_metric(
|
||||
metric_object=metrics.OPC_CONNECTION_STATUS,
|
||||
method='set',
|
||||
value=0,
|
||||
tags={
|
||||
'pod_id': self.engine.pod_id,
|
||||
'server_name': self.server_id,
|
||||
'server_url': self.url,
|
||||
},
|
||||
)
|
||||
await self.engine.emit_metric(
|
||||
metric_object=metrics.OPC_TAGS_SUBSCRIBED,
|
||||
method='set',
|
||||
|
||||
Reference in New Issue
Block a user