test(sql): blackhole/timeout live-gate on a dedicated mssql container
Adds the frozen-peer / BadTimeout gate for the Sql driver — the single highest-value integration test. Mirrors the S7 R2-01 blackhole gate: docker pause a DEDICATED mssql mid-poll, assert the next read surfaces BadTimeout within the client-side operationTimeout (≈3s) and well below the server-side CommandTimeout backstop (30s), the driver degrades (Degraded + host Stopped), and docker unpause recovers to Healthy/Running. - Docker/docker-compose.yml: disposable `otopcua-sql-blackhole` mssql on :14333 (never the shared :14330 ConfigDb server) + one-shot seed. - Docker/seed.sql: the two sample tables. - SqlBlackholeTimeoutTests.cs: env-gated (SQL_BLACKHOLE_ENDPOINT); pauses ONLY the hard-coded container name; skips loudly if the endpoint is the shared port 14330; bounds its own wait so a wedged impl fails, not hangs. Offline: clean skip (no socket, no docker shell-out). Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# Dedicated, DISPOSABLE SQL Server for the Sql driver's blackhole / frozen-peer live gate.
|
||||
#
|
||||
# ── SAFETY ─────────────────────────────────────────────────────────────────────
|
||||
# This stack owns its OWN mssql container, `otopcua-sql-blackhole`, on host port
|
||||
# 14333. The blackhole gate `docker pause`s THIS container by that hard-coded name.
|
||||
# It is NOT — and must never be — the rig's shared always-on SQL Server, which is
|
||||
# `10.100.0.35,14330` and hosts ConfigDb for the whole dev rig. Two independent
|
||||
# guardrails keep them apart:
|
||||
# 1. a distinct container_name (`otopcua-sql-blackhole`) — the pause target is a
|
||||
# compile-time constant in the test, and the shared server is not named this; and
|
||||
# 2. a distinct host port (14333, not 14330) — the test SKIPS LOUDLY if its
|
||||
# endpoint resolves to port 14330.
|
||||
# `restart: "no"` so a paused/killed container stays down rather than respawning.
|
||||
#
|
||||
# ── DEPLOY (from this VM; docker runs on the shared Linux host) ──────────────────
|
||||
# lmxopcua-fix sync sql-blackhole # rsync this Docker/ dir → /opt/otopcua-sql-blackhole/
|
||||
# lmxopcua-fix up sql-blackhole # single-service stack (+ one-shot seed), no profile arg
|
||||
# `lmxopcua-fix` applies the host-side `project=lmxopcua` label; the explicit label
|
||||
# below keeps the stack discoverable even when brought up with plain `docker compose`.
|
||||
#
|
||||
# The one-shot `mssql-seed` service applies seed.sql once the server is healthy, so
|
||||
# the stack is self-seeding. The blackhole test ALSO seeds defensively on connect
|
||||
# (create-if-missing, same schema), so a stack that never ran the seed still works.
|
||||
|
||||
name: otopcua-sql-blackhole
|
||||
|
||||
services:
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
container_name: otopcua-sql-blackhole
|
||||
labels:
|
||||
project: lmxopcua
|
||||
restart: "no"
|
||||
environment:
|
||||
ACCEPT_EULA: "Y"
|
||||
# Dev-only sandbox password for a throwaway container. Override via the shell env
|
||||
# (the same value the test reads from SQL_BLACKHOLE_PASSWORD).
|
||||
MSSQL_SA_PASSWORD: "${SQL_BLACKHOLE_SA_PASSWORD:-Blackhole_dev_pw1}"
|
||||
MSSQL_PID: "Developer"
|
||||
ports:
|
||||
- "14333:1433"
|
||||
healthcheck:
|
||||
# -C trusts the self-signed dev cert; the login succeeding is the readiness signal.
|
||||
test:
|
||||
- "CMD-SHELL"
|
||||
- "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P \"$${MSSQL_SA_PASSWORD}\" -Q 'SELECT 1' || exit 1"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 30s
|
||||
|
||||
# One-shot seed: waits for the server to pass its healthcheck, applies seed.sql, exits.
|
||||
mssql-seed:
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
labels:
|
||||
project: lmxopcua
|
||||
restart: "no"
|
||||
depends_on:
|
||||
mssql:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MSSQL_SA_PASSWORD: "${SQL_BLACKHOLE_SA_PASSWORD:-Blackhole_dev_pw1}"
|
||||
volumes:
|
||||
- ./seed.sql:/seed.sql:ro
|
||||
entrypoint:
|
||||
- "/bin/bash"
|
||||
- "-c"
|
||||
- "/opt/mssql-tools18/bin/sqlcmd -C -S mssql -U sa -P \"$${MSSQL_SA_PASSWORD}\" -i /seed.sql"
|
||||
@@ -0,0 +1,43 @@
|
||||
-- Seed for the Sql driver's blackhole / frozen-peer live gate (dedicated container only).
|
||||
--
|
||||
-- Applied once by the compose `mssql-seed` one-shot against the OWNED
|
||||
-- `otopcua-sql-blackhole` container. The blackhole test (SqlBlackholeTimeoutTests)
|
||||
-- also creates this exact shape defensively on connect, so the two are kept in
|
||||
-- parity: a Good baseline poll before the pause depends on `sqlpoll.TagValues`
|
||||
-- holding the one seeded row.
|
||||
--
|
||||
-- Two sample tables mirror the offline SqlitePollFixture / live SqlPollServerFixture
|
||||
-- shapes so the gate polls something representative, not a bespoke schema.
|
||||
|
||||
IF DB_ID(N'SqlBlackholeFixture') IS NULL CREATE DATABASE [SqlBlackholeFixture];
|
||||
GO
|
||||
|
||||
USE [SqlBlackholeFixture];
|
||||
GO
|
||||
|
||||
IF SCHEMA_ID(N'sqlpoll') IS NULL EXEC(N'CREATE SCHEMA [sqlpoll]');
|
||||
GO
|
||||
|
||||
-- Key-value (EAV) source: tag_name → num_value, with a source timestamp.
|
||||
DROP TABLE IF EXISTS [sqlpoll].[TagValues];
|
||||
CREATE TABLE [sqlpoll].[TagValues] (
|
||||
[tag_name] nvarchar(128) NOT NULL,
|
||||
[num_value] float NULL,
|
||||
[sample_ts] datetime2(3) NOT NULL
|
||||
);
|
||||
INSERT INTO [sqlpoll].[TagValues] ([tag_name], [num_value], [sample_ts])
|
||||
VALUES (N'Line1.Speed', 42.0, '2026-07-24T10:00:00'),
|
||||
(N'Line1.Pressure', 3.5, '2026-07-24T10:00:01');
|
||||
GO
|
||||
|
||||
-- Wide-row source: one row per station, several value columns.
|
||||
DROP TABLE IF EXISTS [sqlpoll].[LatestStatus];
|
||||
CREATE TABLE [sqlpoll].[LatestStatus] (
|
||||
[station_id] int NOT NULL,
|
||||
[oven_temp] float NULL,
|
||||
[pressure] float NULL,
|
||||
[sample_ts] datetime2(3) NOT NULL
|
||||
);
|
||||
INSERT INTO [sqlpoll].[LatestStatus] ([station_id], [oven_temp], [pressure], [sample_ts])
|
||||
VALUES (7, 180.5, 1.2, '2026-07-24T10:00:00');
|
||||
GO
|
||||
Reference in New Issue
Block a user