SIENTIAPDE-1974: feat(read): slot v2 reads via SubscriptionEngine, OPC cert from MinIO
Swaps the legacy OpcManager read path for the gateway's SubscriptionEngine over the shared OpcConnectionPool. The OPC client certificate now always comes from MinIO + Mongo (CertProvider, Basic256Sha256) — no more certs/ shipped in the Gitea repository, so a new version branch no longer breaks the reads. - io/slots.py: parse_slot accepts slot v2 (names[]) and converts the legacy v1 (tag_name + topics; cert_path => use_security, path dropped), so gateway and Orchestrator can be deployed in any order. - managers/server_reader.py: new per-server reader with the OpcManager interface the IngestorManager already drives (connect, create_subscription, subscribe, unsubscribe, check_cycles, check_opc_listenning, shutdown). - IngestorManager owns the read path: lazy-MinIO CertProvider, the pool and a read DataSink, built without touching the network. Slots from Redis are normalized on lease and refresh; a malformed slot is notified and the last good config is kept. - GatewayRuntime borrows the pool (writes/gRPC) instead of building it, and no longer closes it on stop: a Postgres/NATS failure can't stop the reads. - pool: pin/unpin per active subscription so release_idle never evicts a subscribed connection; public evict() for lost servers / changed config. - SubscriptionEngine pins on subscribe, unpins on unsubscribe, and drops a subscription locally even when the server-side delete fails. - DataSink: model_id only written when the name has one, keeping v1 documents identical to the legacy ones. - config: CERT_BUCKET default is the legacy streamlit-connectors bucket (D7). - Removes OpcManager, its tests and scripts/generate-and-push-opc-cert.sh; README/CLAUDE.md document the MinIO certificate and the slot v2 format. Tests: 218 unit tests pass; ruff check/format clean. Local end-to-end against an asyncua server: v1 slot with two topics -> one doc per collection, legacy doc shape, idle sweep keeps the subscription, lost-server shutdown and re-initialization work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,30 +2,25 @@
|
||||
object graph and wires it into the app's lifecycle.
|
||||
|
||||
Reuses the existing ingestor's logger/notification_handler/metrics_controller and
|
||||
its already-connected DataManager (so CertProvider shares the existing Mongo
|
||||
connection instead of opening a second one -- MongoDBRepository.find_one has the
|
||||
exact (collection_name, filters, metadata) signature CertProvider.fetch calls).
|
||||
its already-connected DataManager. The OpcConnectionPool (and its CertProvider,
|
||||
MinIO + Mongo) belongs to the read path in IngestorManager (SIENTIAPDE-1974):
|
||||
reads start in prepare_ingestor, before this runtime exists, and must keep running
|
||||
if anything here fails. The runtime only borrows the pool for writes and gRPC.
|
||||
|
||||
# ponytail: subscription v2 swap + scheduled_tick wiring happen at 1974
|
||||
# integration; when swapping, build ONE SubscriptionEngine per OPC server (its
|
||||
# non_receive_count is engine-wide -- see plan ledger F1 carry-forward).
|
||||
# ponytail: scheduled_tick + gRPC slot_lookup wiring land in the next 1974 RC.
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
|
||||
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
||||
from sientia_do.notifications.models import NotificationLevel
|
||||
from sientia_do.observability.logger import Logger
|
||||
from sientia_do.observability.metrics_controller import MetricsController
|
||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||
from sientia_do.repository.minio_repository import MinioRepository
|
||||
from sientia_do.temporal.activities.postgres import Postgres
|
||||
from sientia_do.utils.connectors_config import build_postgres_config
|
||||
|
||||
from ingestor.adapters.grpc_server import GrpcServer
|
||||
from ingestor.adapters.nats_consumer import NatsWriteConsumer
|
||||
from ingestor.config import GatewayConfig, _env_bool
|
||||
from ingestor.connection.certs import CertProvider
|
||||
from ingestor.config import GatewayConfig
|
||||
from ingestor.connection.pool import OpcConnectionPool
|
||||
from ingestor.engines.active_read import ActiveReadEngine
|
||||
from ingestor.engines.write import WriteEngine
|
||||
@@ -44,6 +39,7 @@ class GatewayRuntime(SientiaMonitoring):
|
||||
self,
|
||||
config: GatewayConfig,
|
||||
data_manager: DataManager,
|
||||
pool: OpcConnectionPool,
|
||||
logger: Logger,
|
||||
notification_handler: NotificationHandler,
|
||||
metrics_controller: MetricsController,
|
||||
@@ -55,26 +51,7 @@ class GatewayRuntime(SientiaMonitoring):
|
||||
self._nats_started = False
|
||||
self._nats_notified = False # notify once per outage, not per 5s retry
|
||||
|
||||
minio = MinioRepository(
|
||||
endpoint=getenv('MINIO_ENDPOINT', 'localhost:9000'),
|
||||
access_key=getenv('MINIO_ACCESS_KEY', 'minioadmin'),
|
||||
secret_key=getenv('MINIO_SECRET_KEY', 'minioadmin'),
|
||||
secure=_env_bool('MINIO_SECURE', True),
|
||||
bucket=config.cert_bucket,
|
||||
logger=logger,
|
||||
notification_handler=notification_handler,
|
||||
metrics_controller=metrics_controller,
|
||||
)
|
||||
cert_provider = CertProvider(
|
||||
mongo=data_manager.mongo_repository,
|
||||
minio=minio,
|
||||
cache_dir=config.cert_cache_dir,
|
||||
bucket=config.cert_bucket,
|
||||
metadata=self.metadata,
|
||||
)
|
||||
self.pool = OpcConnectionPool(
|
||||
cert_provider, logger, notification_handler, metrics_controller
|
||||
)
|
||||
self.pool = pool # owned by IngestorManager (read path); closed there
|
||||
|
||||
postgres = Postgres(
|
||||
**build_postgres_config(),
|
||||
@@ -128,7 +105,8 @@ class GatewayRuntime(SientiaMonitoring):
|
||||
if self._nats_started:
|
||||
await self.nats_consumer.stop()
|
||||
self._nats_started = False
|
||||
await self.pool.close_all()
|
||||
# The pool is not closed here: IngestorManager.shutdown() owns it and runs
|
||||
# after this, once its readers have dropped their subscriptions.
|
||||
|
||||
async def idle_sweep(self) -> None:
|
||||
await self.pool.release_idle(self.config.idle_conn_max_s)
|
||||
|
||||
Reference in New Issue
Block a user