- 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>
88 lines
2.3 KiB
Python
88 lines
2.3 KiB
Python
import pytest
|
|
|
|
from ingestor.config import GatewayConfig
|
|
|
|
_ENV_VARS = (
|
|
'NATS_URL',
|
|
'NATS_WRITE_SUBJECT',
|
|
'NATS_QUEUE',
|
|
'GRPC_ENABLED',
|
|
'GRPC_PORT',
|
|
'CERT_BUCKET',
|
|
'CERT_CACHE_DIR',
|
|
'WRITE_TIMESTAMP',
|
|
'WRITE_QUALITY',
|
|
'WRITE_MIN_PAUSE_S',
|
|
'IDLE_CONN_MAX_S',
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clean_env(monkeypatch):
|
|
for var in _ENV_VARS:
|
|
monkeypatch.delenv(var, raising=False)
|
|
|
|
|
|
def test_from_env_defaults():
|
|
cfg = GatewayConfig.from_env()
|
|
|
|
assert cfg.nats_url == 'nats://localhost:4222'
|
|
assert cfg.nats_write_subject == 'opc.write'
|
|
assert cfg.nats_queue == 'gateway-writers'
|
|
assert cfg.grpc_enabled is False
|
|
assert cfg.grpc_port == 50051
|
|
assert cfg.cert_bucket == 'streamlit-connectors'
|
|
assert cfg.cert_cache_dir == '/tmp/opc-certs' # noqa: S108
|
|
assert cfg.write_timestamp is True
|
|
assert cfg.write_quality is True
|
|
assert cfg.write_min_pause_s == 0.0
|
|
assert cfg.idle_conn_max_s == 300.0
|
|
|
|
|
|
def test_from_env_overrides(monkeypatch):
|
|
monkeypatch.setenv('GRPC_ENABLED', 'true')
|
|
monkeypatch.setenv('GRPC_PORT', '6000')
|
|
monkeypatch.setenv('WRITE_MIN_PAUSE_S', '0.5')
|
|
monkeypatch.setenv('WRITE_QUALITY', 'false')
|
|
|
|
cfg = GatewayConfig.from_env()
|
|
|
|
assert cfg.grpc_enabled is True
|
|
assert cfg.grpc_port == 6000
|
|
assert cfg.write_min_pause_s == 0.5
|
|
assert cfg.write_quality is False
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'value,expected',
|
|
[
|
|
('0', False),
|
|
('no', False),
|
|
('FALSE', False),
|
|
('1', True),
|
|
('YES', True),
|
|
],
|
|
)
|
|
def test_bool_parsing_edge_cases(monkeypatch, value, expected):
|
|
monkeypatch.setenv('GRPC_ENABLED', value)
|
|
|
|
cfg = GatewayConfig.from_env()
|
|
|
|
assert cfg.grpc_enabled is expected
|
|
|
|
|
|
def test_certs_from_env_ignores_malformed_numeric_vars(monkeypatch):
|
|
import pytest
|
|
|
|
monkeypatch.setenv('GRPC_PORT', '')
|
|
monkeypatch.setenv('IDLE_CONN_MAX_S', 'abc')
|
|
monkeypatch.setenv('CERT_BUCKET', 'my-bucket')
|
|
monkeypatch.setenv('CERT_CACHE_DIR', 'certs-cache')
|
|
|
|
cfg = GatewayConfig.certs_from_env()
|
|
|
|
assert cfg.cert_bucket == 'my-bucket'
|
|
assert cfg.cert_cache_dir == 'certs-cache'
|
|
with pytest.raises(ValueError):
|
|
GatewayConfig.from_env() # the full config still fails, only the runtime uses it
|