From e505bae7b52488bf775c00fbb24b0c408f97bbbe Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sat, 11 Apr 2026 01:38:50 +0000 Subject: [PATCH 1/3] refactor: use VM_HOST env var instead of hardcoded RAC VM IPs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all hardcoded 192.168.122.x IPs with ${VM_HOST} env var. VM IP is auto-detected by vm-env.sh via virsh — no manual update needed when VM IP changes (e.g. new VM deployment). - .properties files: use Quarkus ${VM_HOST} substitution - docker-compose: use ${VM_HOST} in commands - rac.sh: fail-fast with ${VM_HOST:?} instead of silent default - db-check.py: require ORACLE_HOST, no fallback IP - .env: remove DB_CONN (set by rac driver) - vm-env.sh: remove stale IP mismatch validation --- .../application-logminer-kafka.properties | 2 +- .../config/application-logminer.properties | 2 +- .../config/application-olr-kafka.properties | 4 +-- .../rac/config/application-olr.properties | 4 +-- tests/dbz-twin/rac/db-check.py | 7 +++-- .../config/application-logminer.properties | 2 +- .../perf/config/application-olr.properties | 4 +-- tests/dbz-twin/rac/perf/config/prometheus.yml | 6 ++-- tests/dbz-twin/rac/perf/docker-compose.yaml | 2 +- tests/environments/rac/.env | 3 +- tests/environments/rac/vm-env.sh | 28 ------------------- tests/sql/scripts/drivers/rac.sh | 2 +- 12 files changed, 22 insertions(+), 44 deletions(-) diff --git a/tests/dbz-twin/rac/config/application-logminer-kafka.properties b/tests/dbz-twin/rac/config/application-logminer-kafka.properties index 7732d386..06bd8005 100644 --- a/tests/dbz-twin/rac/config/application-logminer-kafka.properties +++ b/tests/dbz-twin/rac/config/application-logminer-kafka.properties @@ -13,7 +13,7 @@ debezium.format.key.schemas.enable=false debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector debezium.source.database.connection.adapter=logminer -debezium.source.database.hostname=192.168.122.130 +debezium.source.database.hostname=${VM_HOST} debezium.source.database.port=1521 debezium.source.database.user=c##dbzuser debezium.source.database.password=dbz diff --git a/tests/dbz-twin/rac/config/application-logminer.properties b/tests/dbz-twin/rac/config/application-logminer.properties index eee75dea..854953b9 100644 --- a/tests/dbz-twin/rac/config/application-logminer.properties +++ b/tests/dbz-twin/rac/config/application-logminer.properties @@ -10,7 +10,7 @@ debezium.format.key.schemas.enable=false debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector debezium.source.database.connection.adapter=logminer -debezium.source.database.hostname=192.168.122.130 +debezium.source.database.hostname=${VM_HOST} debezium.source.database.port=1521 debezium.source.database.user=c##dbzuser debezium.source.database.password=dbz diff --git a/tests/dbz-twin/rac/config/application-olr-kafka.properties b/tests/dbz-twin/rac/config/application-olr-kafka.properties index c88ead6e..cd7fe978 100644 --- a/tests/dbz-twin/rac/config/application-olr-kafka.properties +++ b/tests/dbz-twin/rac/config/application-olr-kafka.properties @@ -14,9 +14,9 @@ debezium.format.key.schemas.enable=false debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector debezium.source.database.connection.adapter=olr debezium.source.openlogreplicator.source=ORCLCDB -debezium.source.openlogreplicator.host=192.168.122.130 +debezium.source.openlogreplicator.host=${VM_HOST} debezium.source.openlogreplicator.port=5000 -debezium.source.database.hostname=192.168.122.130 +debezium.source.database.hostname=${VM_HOST} debezium.source.database.port=1521 debezium.source.database.user=c##dbzuser debezium.source.database.password=dbz diff --git a/tests/dbz-twin/rac/config/application-olr.properties b/tests/dbz-twin/rac/config/application-olr.properties index 072be2bb..76730da7 100644 --- a/tests/dbz-twin/rac/config/application-olr.properties +++ b/tests/dbz-twin/rac/config/application-olr.properties @@ -11,9 +11,9 @@ debezium.format.key.schemas.enable=false debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector debezium.source.database.connection.adapter=olr debezium.source.openlogreplicator.source=ORCLCDB -debezium.source.openlogreplicator.host=192.168.122.130 +debezium.source.openlogreplicator.host=${VM_HOST} debezium.source.openlogreplicator.port=5000 -debezium.source.database.hostname=192.168.122.130 +debezium.source.database.hostname=${VM_HOST} debezium.source.database.port=1521 debezium.source.database.user=c##dbzuser debezium.source.database.password=dbz diff --git a/tests/dbz-twin/rac/db-check.py b/tests/dbz-twin/rac/db-check.py index 2fcc9c04..2cdc6a42 100644 --- a/tests/dbz-twin/rac/db-check.py +++ b/tests/dbz-twin/rac/db-check.py @@ -6,7 +6,7 @@ Environment variables: SQLITE_DB — SQLite database path (default: /app/data/fuzz.db) - ORACLE_HOST — Oracle host (default: 192.168.122.130) + ORACLE_HOST — Oracle host (required, set by fuzz-test.sh from VM_HOST) ORACLE_DSN — Full Oracle DSN (overrides ORACLE_HOST) """ @@ -314,7 +314,10 @@ def print_results(name, matched, missing, extra, diffs): def main(): sqlite_path = os.environ.get('SQLITE_DB', '/app/data/fuzz.db') - oracle_host = os.environ.get('ORACLE_HOST', '192.168.122.130') + oracle_host = os.environ.get('ORACLE_HOST') + if not oracle_host and not os.environ.get('ORACLE_DSN'): + print("ERROR: ORACLE_HOST or ORACLE_DSN must be set", file=sys.stderr) + sys.exit(1) oracle_dsn = os.environ.get('ORACLE_DSN', f"olr_test/olr_test@{oracle_host}:1521/ORCLPDB") diff --git a/tests/dbz-twin/rac/perf/config/application-logminer.properties b/tests/dbz-twin/rac/perf/config/application-logminer.properties index ca05634d..5e310b23 100644 --- a/tests/dbz-twin/rac/perf/config/application-logminer.properties +++ b/tests/dbz-twin/rac/perf/config/application-logminer.properties @@ -9,7 +9,7 @@ debezium.format.key.schemas.enable=false debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector debezium.source.database.connection.adapter=logminer -debezium.source.database.hostname=192.168.122.130 +debezium.source.database.hostname=${VM_HOST} debezium.source.database.port=1521 debezium.source.database.user=c##dbzuser debezium.source.database.password=dbz diff --git a/tests/dbz-twin/rac/perf/config/application-olr.properties b/tests/dbz-twin/rac/perf/config/application-olr.properties index a7014c7e..1e85fd1a 100644 --- a/tests/dbz-twin/rac/perf/config/application-olr.properties +++ b/tests/dbz-twin/rac/perf/config/application-olr.properties @@ -10,9 +10,9 @@ debezium.format.key.schemas.enable=false debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector debezium.source.database.connection.adapter=olr debezium.source.openlogreplicator.source=ORCLCDB -debezium.source.openlogreplicator.host=192.168.122.130 +debezium.source.openlogreplicator.host=${VM_HOST} debezium.source.openlogreplicator.port=5000 -debezium.source.database.hostname=192.168.122.130 +debezium.source.database.hostname=${VM_HOST} debezium.source.database.port=1521 debezium.source.database.user=c##dbzuser debezium.source.database.password=dbz diff --git a/tests/dbz-twin/rac/perf/config/prometheus.yml b/tests/dbz-twin/rac/perf/config/prometheus.yml index 55f81270..536e52cc 100644 --- a/tests/dbz-twin/rac/perf/config/prometheus.yml +++ b/tests/dbz-twin/rac/perf/config/prometheus.yml @@ -1,10 +1,12 @@ +# Auto-generated by perf/run.sh — do not edit manually. +# VM_HOST is substituted at runtime from vm-env.sh. global: scrape_interval: 5s scrape_configs: - job_name: 'node-exporter' static_configs: - - targets: ['192.168.122.130:9100'] + - targets: ['${VM_HOST}:9100'] - job_name: 'cadvisor' static_configs: - - targets: ['192.168.122.130:9101'] + - targets: ['${VM_HOST}:9101'] diff --git a/tests/dbz-twin/rac/perf/docker-compose.yaml b/tests/dbz-twin/rac/perf/docker-compose.yaml index beec67f2..63e428de 100644 --- a/tests/dbz-twin/rac/perf/docker-compose.yaml +++ b/tests/dbz-twin/rac/perf/docker-compose.yaml @@ -44,7 +44,7 @@ services: # Override with: docker compose run swingbench -uc 8 -rt 01:00.00 command: - "-cs" - - "//192.168.122.130:1521/ORCLPDB" + - "//${VM_HOST}:1521/ORCLPDB" - "-u" - "soe" - "-p" diff --git a/tests/environments/rac/.env b/tests/environments/rac/.env index 8b44d91c..ecb4a17e 100644 --- a/tests/environments/rac/.env +++ b/tests/environments/rac/.env @@ -1,3 +1,4 @@ -DB_CONN=olr_test/olr_test@//192.168.122.130:1521/ORCLPDB +# DB_CONN is set by the rac driver (tests/sql/scripts/drivers/rac.sh) +# VM_HOST is auto-detected by vm-env.sh PDB_NAME=ORCLPDB INCLUDE_TAGS=rac diff --git a/tests/environments/rac/vm-env.sh b/tests/environments/rac/vm-env.sh index 8e736da9..27f4f15d 100644 --- a/tests/environments/rac/vm-env.sh +++ b/tests/environments/rac/vm-env.sh @@ -24,32 +24,4 @@ fi _SSH_OPTS="-i $VM_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR" -# Validate config files match detected IP -_VM_ENV_MISMATCH=0 -_check_ip() { - local file="$1" - if [[ -f "$file" ]]; then - local found - found=$(grep -v '^\s*#\|^\s*//' "$file" | grep -oP '192\.168\.122\.\d+' | sort -u || true) - for ip in $found; do - if [[ "$ip" != "$VM_HOST" ]]; then - echo " MISMATCH: $file has $ip (expected $VM_HOST)" >&2 - _VM_ENV_MISMATCH=1 - fi - done - fi -} - -_check_ip "$_RAC_ENV_DIR/.env" -_check_ip "$_PROJECT_ROOT/tests/sql/scripts/drivers/rac.sh" -_check_ip "$_RAC_ENV_DIR/debezium/config/application-logminer.properties" -_check_ip "$_RAC_ENV_DIR/debezium/config/application-olr.properties" -_check_ip "$_RAC_ENV_DIR/debezium/config/olr-config.json" - -if [[ $_VM_ENV_MISMATCH -ne 0 ]]; then - echo "ERROR: RAC VM IP is $VM_HOST but config files have stale IPs." >&2 - echo " Fix with: sed -i 's/192.168.122.[0-9]\\+/$VM_HOST/g' " >&2 - return 1 2>/dev/null || exit 1 -fi - export VM_HOST VM_KEY VM_USER _SSH_OPTS diff --git a/tests/sql/scripts/drivers/rac.sh b/tests/sql/scripts/drivers/rac.sh index 75824b81..b8ebb41f 100644 --- a/tests/sql/scripts/drivers/rac.sh +++ b/tests/sql/scripts/drivers/rac.sh @@ -36,7 +36,7 @@ DB_CONN="olr_test/olr_test@//racnodep1:1521/ORCLPDB" source "$SCRIPT_DIR/drivers/base.sh" # ---- RAC configuration ---- -VM_HOST="${VM_HOST:-192.168.122.130}" +VM_HOST="${VM_HOST:?VM_HOST is required — source tests/environments/rac/vm-env.sh}" VM_KEY="${VM_KEY:-$PROJECT_ROOT/oracle-rac/assets/vm-key}" VM_USER="${VM_USER:-root}" OLR_IMAGE="${OLR_IMAGE:-olr-dev:latest}" From 97e5c9068e1081605498d40fb9f0d181f19642f4 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sat, 11 Apr 2026 01:39:08 +0000 Subject: [PATCH 2/3] feat: hybrid 3-connector fuzz test with patched Debezium image Switch to rophy/debezium-server:3.5.0-2a7978c0af which includes: - debezium-config JAR fix (missing in stock 3.5.0.Final) - mergeLogsByPrecedence RAC fix (thread-aware dedup) Implement hybrid architecture: OLR for non-LOB tables + LogMiner for LOB tables on the "actual" side. - Add dbz-lob-logminer service (LOB-only LogMiner connector) - OLR config: skip-lob-tables=1 - Consumer: subscribe to 3 topics, route olr-lob-events to OLR side - Validator: remove KNOWN_LOB_TABLES exemption, LOB mismatches are real failures with hybrid setup - fuzz-test.sh: 3-connector offset seeding, wait, logs - Remove restart: unless-stopped, remove Beta1 JAR mounts --- .../application-lob-logminer-kafka.properties | 43 +++++++++++++++++++ tests/dbz-twin/rac/config/olr-config.json | 3 +- tests/dbz-twin/rac/docker-compose-fuzz.yaml | 26 +++++++++-- tests/dbz-twin/rac/fuzz-test.sh | 34 ++++++++------- tests/dbz-twin/rac/kafka-consumer.py | 18 +++++--- tests/dbz-twin/rac/validator.py | 40 ++++++++--------- 6 files changed, 115 insertions(+), 49 deletions(-) create mode 100644 tests/dbz-twin/rac/config/application-lob-logminer-kafka.properties diff --git a/tests/dbz-twin/rac/config/application-lob-logminer-kafka.properties b/tests/dbz-twin/rac/config/application-lob-logminer-kafka.properties new file mode 100644 index 00000000..680b4335 --- /dev/null +++ b/tests/dbz-twin/rac/config/application-lob-logminer-kafka.properties @@ -0,0 +1,43 @@ +# Debezium Server — LogMiner adapter → Kafka sink (LOB tables only) +# Complements OLR by handling LOB tables that OLR skips (skip-lob-tables=1). +# Events route to olr-lob-events topic and merge into OLR's "actual" stream. +quarkus.http.port=8083 +debezium.sink.type=kafka +debezium.sink.kafka.producer.bootstrap.servers=localhost:9092 +debezium.sink.kafka.producer.key.serializer=org.apache.kafka.common.serialization.StringSerializer +debezium.sink.kafka.producer.value.serializer=org.apache.kafka.common.serialization.StringSerializer + +debezium.format.value=json +debezium.format.value.schemas.enable=false +debezium.format.key=json +debezium.format.key.schemas.enable=false + +debezium.source.connector.class=io.debezium.connector.oracle.OracleConnector +debezium.source.database.connection.adapter=logminer +debezium.source.database.hostname=${VM_HOST} +debezium.source.database.port=1521 +debezium.source.database.user=c##dbzuser +debezium.source.database.password=dbz +debezium.source.database.dbname=ORCLCDB +debezium.source.database.pdb.name=ORCLPDB +debezium.source.topic.prefix=olr-lob +debezium.source.schema.include.list=OLR_TEST +debezium.source.table.include.list=OLR_TEST.FUZZ_LOB +debezium.source.snapshot.mode=recovery +debezium.source.log.mining.strategy=online_catalog +debezium.source.lob.enabled=true + +# Route all tables to a single topic for ordered delivery +debezium.transforms=route +debezium.transforms.route.type=org.apache.kafka.connect.transforms.RegexRouter +debezium.transforms.route.regex=.* +debezium.transforms.route.replacement=olr-lob-events + +debezium.source.offset.storage=org.apache.kafka.connect.storage.KafkaOffsetBackingStore +debezium.source.offset.storage.topic=dbz-olr-lob-offsets +debezium.source.offset.storage.partitions=1 +debezium.source.offset.storage.replication.factor=1 +debezium.source.bootstrap.servers=localhost:9092 +debezium.source.offset.flush.interval.ms=0 +debezium.source.schema.history.internal=io.debezium.storage.file.history.FileSchemaHistory +debezium.source.schema.history.internal.file.filename=/debezium/data/schema-history.dat diff --git a/tests/dbz-twin/rac/config/olr-config.json b/tests/dbz-twin/rac/config/olr-config.json index d2f1a4dc..c19e65e9 100644 --- a/tests/dbz-twin/rac/config/olr-config.json +++ b/tests/dbz-twin/rac/config/olr-config.json @@ -27,7 +27,8 @@ "scn-type": 1, "timestamp-type": 1, "user-type": 0, - "redo-thread": 0 + "redo-thread": 0, + "skip-lob-tables": 1 }, "filter": { "table": [ diff --git a/tests/dbz-twin/rac/docker-compose-fuzz.yaml b/tests/dbz-twin/rac/docker-compose-fuzz.yaml index d4eaea38..999fa9df 100644 --- a/tests/dbz-twin/rac/docker-compose-fuzz.yaml +++ b/tests/dbz-twin/rac/docker-compose-fuzz.yaml @@ -22,9 +22,11 @@ services: start_period: 15s dbz-logminer: - image: quay.io/debezium/server:3.5.0.Beta1 + image: rophy/debezium-server:3.5.0-2a7978c0af container_name: fuzz-dbz-logminer network_mode: host + environment: + VM_HOST: ${VM_HOST:?VM_HOST is required} depends_on: kafka: condition: service_healthy @@ -34,19 +36,33 @@ services: - dbz-logminer-data:/debezium/data dbz-olr: - image: quay.io/debezium/server:3.5.0.Beta1 + image: rophy/debezium-server:3.5.0-2a7978c0af container_name: fuzz-dbz-olr network_mode: host - restart: unless-stopped + environment: + VM_HOST: ${VM_HOST:?VM_HOST is required} depends_on: kafka: condition: service_healthy volumes: - ./config/application-olr-kafka.properties:/debezium/config/application.properties:ro - ../lib/ojdbc8.jar:/debezium/lib/ojdbc8.jar:ro - - ../lib/debezium-connector-oracle-3.5.0.Beta1.jar:/debezium/lib/debezium-connector-oracle-3.5.0.Beta1.jar:ro - dbz-olr-data:/debezium/data + dbz-lob-logminer: + image: rophy/debezium-server:3.5.0-2a7978c0af + container_name: fuzz-dbz-lob-logminer + network_mode: host + environment: + VM_HOST: ${VM_HOST:?VM_HOST is required} + depends_on: + kafka: + condition: service_healthy + volumes: + - ./config/application-lob-logminer-kafka.properties:/debezium/config/application.properties:ro + - ../lib/ojdbc8.jar:/debezium/lib/ojdbc8.jar:ro + - dbz-lob-logminer-data:/debezium/data + consumer: build: context: . @@ -63,6 +79,7 @@ services: SQLITE_DB: /app/data/fuzz.db LM_TOPIC: lm-events OLR_TOPIC: olr-events + OLR_LOB_TOPIC: olr-lob-events volumes: - ./kafka-consumer.py:/app/kafka-consumer.py:ro - fuzz-data:/app/data @@ -90,4 +107,5 @@ services: volumes: dbz-logminer-data: dbz-olr-data: + dbz-lob-logminer-data: fuzz-data: diff --git a/tests/dbz-twin/rac/fuzz-test.sh b/tests/dbz-twin/rac/fuzz-test.sh index 362d3f37..b1662fa8 100755 --- a/tests/dbz-twin/rac/fuzz-test.sh +++ b/tests/dbz-twin/rac/fuzz-test.sh @@ -84,7 +84,7 @@ _seed_debezium_offsets() { # topic_prefix matches debezium.source.topic.prefix in each connector config # offset_topic matches debezium.source.offset.storage.topic - local -A topics=( [logminer]=dbz-lm-offsets [olr]=dbz-olr-offsets ) + local -A topics=( [logminer]=dbz-lm-offsets [olr]=dbz-olr-offsets [olr-lob]=dbz-olr-lob-offsets ) for topic_prefix in "${!topics[@]}"; do local offset_topic="${topics[$topic_prefix]}" local offset_key="[\"kafka\",{\"server\":\"${topic_prefix}\"}]" @@ -212,15 +212,16 @@ action_up() { # Wait for Debezium connectors echo " Waiting for Debezium connectors..." - for i in $(seq 1 60); do - LM_OK=false; OLR_OK=false - docker logs fuzz-dbz-logminer 2>&1 | tail -10 | grep -q "Starting streaming" && LM_OK=true - docker logs fuzz-dbz-olr 2>&1 | tail -10 | grep -q "streaming client started\|Starting streaming" && OLR_OK=true - if $LM_OK && $OLR_OK; then - echo " Debezium: ready" + for i in $(seq 1 90); do + LM_OK=false; OLR_OK=false; LOB_LM_OK=false + docker logs fuzz-dbz-logminer 2>&1 | grep -q "Starting streaming" && LM_OK=true + docker logs fuzz-dbz-olr 2>&1 | grep -q "streaming client started\|Starting streaming" && OLR_OK=true + docker logs fuzz-dbz-lob-logminer 2>&1 | grep -q "Starting streaming" && LOB_LM_OK=true + if $LM_OK && $OLR_OK && $LOB_LM_OK; then + echo " Debezium: ready (3 connectors)" break fi - [[ $i -eq 60 ]] && { echo "ERROR: Debezium connectors did not start" >&2; exit 1; } + [[ $i -eq 90 ]] && { echo "ERROR: Debezium connectors did not start" >&2; exit 1; } sleep 2 done @@ -427,19 +428,20 @@ action_validate() { action_logs() { local component="${1:-}" case "$component" in - kafka) docker logs fuzz-kafka 2>&1 ;; - logminer) docker logs fuzz-dbz-logminer 2>&1 ;; - olr) docker logs fuzz-dbz-olr 2>&1 ;; - consumer) docker logs fuzz-consumer 2>&1 ;; - validator) docker logs fuzz-validator 2>&1 ;; - olr-vm) ssh $_SSH_OPTS "${VM_USER}@${VM_HOST}" "podman logs $OLR_CONTAINER" 2>/dev/null ;; + kafka) docker logs fuzz-kafka 2>&1 ;; + logminer) docker logs fuzz-dbz-logminer 2>&1 ;; + olr) docker logs fuzz-dbz-olr 2>&1 ;; + lob-logminer) docker logs fuzz-dbz-lob-logminer 2>&1 ;; + consumer) docker logs fuzz-consumer 2>&1 ;; + validator) docker logs fuzz-validator 2>&1 ;; + olr-vm) ssh $_SSH_OPTS "${VM_USER}@${VM_HOST}" "podman logs $OLR_CONTAINER" 2>/dev/null ;; "") echo "Usage: $0 logs " - echo "Components: kafka, logminer, olr, consumer, validator, olr-vm" + echo "Components: kafka, logminer, olr, lob-logminer, consumer, validator, olr-vm" ;; *) echo "Unknown component: $component" >&2 - echo "Components: kafka, logminer, olr, consumer, validator, olr-vm" + echo "Components: kafka, logminer, olr, lob-logminer, consumer, validator, olr-vm" exit 1 ;; esac diff --git a/tests/dbz-twin/rac/kafka-consumer.py b/tests/dbz-twin/rac/kafka-consumer.py index 8072cc5b..191e3303 100644 --- a/tests/dbz-twin/rac/kafka-consumer.py +++ b/tests/dbz-twin/rac/kafka-consumer.py @@ -94,13 +94,18 @@ def extract_event_info(event): LM_TOPIC = os.environ.get('LM_TOPIC', 'lm-events') OLR_TOPIC = os.environ.get('OLR_TOPIC', 'olr-events') +OLR_LOB_TOPIC = os.environ.get('OLR_LOB_TOPIC', 'olr-lob-events') def determine_adapter(topic): - """Determine adapter (logminer or olr) from Kafka topic name.""" + """Determine adapter (logminer or olr) from Kafka topic name. + + The OLR LOB topic (LogMiner for LOB tables) is treated as 'olr' because + it complements OLR on the "actual" side of the comparison. + """ if topic == LM_TOPIC: return 'logminer' - elif topic == OLR_TOPIC: + elif topic in (OLR_TOPIC, OLR_LOB_TOPIC): return 'olr' # Fallback for per-table topics if topic.startswith('logminer'): @@ -139,18 +144,19 @@ def main(): sys.exit(1) # Wait for topics to appear, then subscribe - print(f"Waiting for topics: {LM_TOPIC}, {OLR_TOPIC}...", flush=True) + all_topics = [LM_TOPIC, OLR_TOPIC, OLR_LOB_TOPIC] + print(f"Waiting for topics: {', '.join(all_topics)}...", flush=True) for attempt in range(60): topics = consumer.topics() if LM_TOPIC in topics or OLR_TOPIC in topics: - print(f" Found topics: {[t for t in (LM_TOPIC, OLR_TOPIC) if t in topics]}", flush=True) + print(f" Found topics: {[t for t in all_topics if t in topics]}", flush=True) break time.sleep(5) - consumer.subscribe([LM_TOPIC, OLR_TOPIC]) + consumer.subscribe(all_topics) # Force metadata refresh consumer.poll(timeout_ms=1000) - print(f"Subscribed to {LM_TOPIC} and {OLR_TOPIC}", flush=True) + print(f"Subscribed to {', '.join(all_topics)}", flush=True) # Track per-event_id sequence numbers for LOB split handling. lm_seq = {} # event_id -> next seq diff --git a/tests/dbz-twin/rac/validator.py b/tests/dbz-twin/rac/validator.py index 7fde1bfa..d3a29214 100644 --- a/tests/dbz-twin/rac/validator.py +++ b/tests/dbz-twin/rac/validator.py @@ -24,9 +24,10 @@ POLL_INTERVAL = int(os.environ.get('POLL_INTERVAL', '10')) IDLE_TIMEOUT = int(os.environ.get('IDLE_TIMEOUT', '120')) -# Known LOB phantom transaction issues (olr#26, olr#10) -# These produce expected mismatches — report but don't fail -KNOWN_LOB_TABLES = {'FUZZ_LOB'} +# LOB tables that use final-state replay for comparison. +# With the hybrid setup (OLR for non-LOB + LogMiner for LOB), these tables +# should match exactly. Mismatches are treated as real failures. +LOB_TABLES = {'FUZZ_LOB'} def normalize_value(v): @@ -132,7 +133,6 @@ def main(): total_validated = 0 total_matched = 0 total_mismatches = 0 - total_lob_known = 0 # Known LOB issues (expected) total_missing_lm = 0 total_missing_olr = 0 total_tail_olr = 0 # OLR ahead of LM at drain time (not a bug) @@ -245,14 +245,12 @@ def main(): "SELECT table_name FROM olr_events WHERE event_id = ? LIMIT 1", (eid,)).fetchone() event_table = tbl_row['table_name'] if tbl_row else '?' - is_lob = event_table in KNOWN_LOB_TABLES + is_lob = event_table in LOB_TABLES if in_lm and not in_olr: total_missing_olr += 1 if is_tail: total_tail_lm += 1 - elif is_lob: - total_lob_known += 1 else: total_mismatches += 1 print(f"[MISSING_OLR] {eid} ({event_table})", flush=True) @@ -263,8 +261,6 @@ def main(): total_missing_lm += 1 if is_tail: total_tail_olr += 1 - elif is_lob: - total_lob_known += 1 else: total_mismatches += 1 print(f"[EXTRA_OLR] {eid} ({event_table})", flush=True) @@ -283,20 +279,26 @@ def main(): if is_lob: # LOB tables: replay ops into final state, compare end result. - # LogMiner merges INSERT + LOB_WRITE into a single record (L2), - # and OLR may have extra/fewer intermediate events due to - # phantom undo (#15). Comparing final state avoids both issues. + # Both sides use LogMiner (expected=full LM, actual=LOB-only LM), + # so they should produce identical final state. lm_state, lm_exists = replay_final_state(lm_recs) olr_state, olr_exists = replay_final_state(olr_recs) if lm_exists != olr_exists: - total_lob_known += 1 + total_mismatches += 1 + print(f"[LOB_EXISTENCE] {eid} ({event_table}): " + f"LM exists={lm_exists} OLR exists={olr_exists}", + flush=True) total_validated += 1 else: diffs = compare_values(lm_state, olr_state, event_table, 'after') if diffs: - total_lob_known += 1 + total_mismatches += 1 + print(f"[LOB_VALUE_DIFF] {eid} ({event_table}):", + flush=True) + for d in diffs[:5]: + print(d, flush=True) else: total_matched += 1 total_validated += 1 @@ -373,7 +375,7 @@ def main(): tail_str = (f" tail_olr={total_tail_olr} tail_lm={total_tail_lm}" if total_tail_olr or total_tail_lm else "") print(f"[validator] validated={total_validated} matched={total_matched} " - f"mismatches={total_mismatches} lob_known={total_lob_known} " + f"mismatches={total_mismatches} " f"missing_olr={total_missing_olr} extra_olr={total_missing_lm}" f"{tail_str} " f"lm_total={lm_count} olr_total={olr_count} " @@ -391,7 +393,6 @@ def main(): print(f" Total validated: {total_validated}", flush=True) print(f" Matched: {total_matched}", flush=True) print(f" Mismatches: {total_mismatches}", flush=True) - print(f" LOB known issues: {total_lob_known}", flush=True) print(f" Missing from OLR: {total_missing_olr}", flush=True) print(f" Extra in OLR: {total_missing_lm}", flush=True) if total_tail_olr or total_tail_lm: @@ -404,13 +405,8 @@ def main(): sys.exit(1) else: print("\n RESULT: PASS", flush=True) - qualifiers = [] - if total_lob_known > 0: - qualifiers.append(f"{total_lob_known} known LOB issues") if total_tail_olr + total_tail_lm > 0: - qualifiers.append(f"{total_tail_olr + total_tail_lm} tail events") - if qualifiers: - print(f" ({', '.join(qualifiers)})", flush=True) + print(f" ({total_tail_olr + total_tail_lm} tail events)", flush=True) sys.exit(0) From 591504c965fb1856e772d396eb0e7a0cf900b9ed Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sat, 11 Apr 2026 01:59:09 +0000 Subject: [PATCH 3/3] fix: address CodeRabbit review on PR #19 - fuzz-test.sh: add lob-logminer to help text, show down-before-up workflow - kafka-consumer.py: wait for all 3 topics before subscribing, fail on missing - perf/docker-compose.yaml: use ${VM_HOST:?} for fail-fast on missing env var - validator.py: normalize table name (strip schema, uppercase) for LOB detection - .env: clarify VM_HOST must be sourced from vm-env.sh - rac.sh: auto-source vm-env.sh when VM_HOST is not set --- tests/dbz-twin/rac/fuzz-test.sh | 7 ++++--- tests/dbz-twin/rac/kafka-consumer.py | 10 +++++++--- tests/dbz-twin/rac/perf/docker-compose.yaml | 2 +- tests/dbz-twin/rac/validator.py | 2 +- tests/environments/rac/.env | 2 +- tests/sql/scripts/drivers/rac.sh | 4 ++++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/dbz-twin/rac/fuzz-test.sh b/tests/dbz-twin/rac/fuzz-test.sh index b1662fa8..8959bbe4 100755 --- a/tests/dbz-twin/rac/fuzz-test.sh +++ b/tests/dbz-twin/rac/fuzz-test.sh @@ -12,13 +12,14 @@ # run [duration-min] Deploy fuzz workload and run for N minutes (default: 30) # status Show consumer/validator status and OLR memory # validate Run validator (wait for idle timeout, report results) -# logs [component] Show logs (kafka, logminer, olr, consumer, validator, olr-vm) +# logs [component] Show logs (kafka, logminer, olr, lob-logminer, consumer, validator, olr-vm) # down Stop and remove all containers + volumes # help Show this help # # Typical workflow: -# ./fuzz-test.sh up # start infrastructure -# ./fuzz-test.sh run 60 # run 60-minute fuzz workload +# ./fuzz-test.sh down # clean up any previous run +# ./fuzz-test.sh up # start infrastructure +# ./fuzz-test.sh run 60 # run 60-minute fuzz workload # ./fuzz-test.sh status # check progress # ./fuzz-test.sh validate # wait for drain + validate # ./fuzz-test.sh logs validator # investigate mismatches diff --git a/tests/dbz-twin/rac/kafka-consumer.py b/tests/dbz-twin/rac/kafka-consumer.py index 191e3303..086cc514 100644 --- a/tests/dbz-twin/rac/kafka-consumer.py +++ b/tests/dbz-twin/rac/kafka-consumer.py @@ -146,12 +146,16 @@ def main(): # Wait for topics to appear, then subscribe all_topics = [LM_TOPIC, OLR_TOPIC, OLR_LOB_TOPIC] print(f"Waiting for topics: {', '.join(all_topics)}...", flush=True) - for attempt in range(60): + for _ in range(60): topics = consumer.topics() - if LM_TOPIC in topics or OLR_TOPIC in topics: - print(f" Found topics: {[t for t in all_topics if t in topics]}", flush=True) + if all(t in topics for t in all_topics): + print(f" Found all topics: {all_topics}", flush=True) break time.sleep(5) + else: + missing = [t for t in all_topics if t not in topics] + print(f"ERROR: Missing Kafka topics after 5 min: {missing}", flush=True) + sys.exit(1) consumer.subscribe(all_topics) # Force metadata refresh diff --git a/tests/dbz-twin/rac/perf/docker-compose.yaml b/tests/dbz-twin/rac/perf/docker-compose.yaml index 63e428de..8f93b583 100644 --- a/tests/dbz-twin/rac/perf/docker-compose.yaml +++ b/tests/dbz-twin/rac/perf/docker-compose.yaml @@ -44,7 +44,7 @@ services: # Override with: docker compose run swingbench -uc 8 -rt 01:00.00 command: - "-cs" - - "//${VM_HOST}:1521/ORCLPDB" + - "//${VM_HOST:?VM_HOST is required}:1521/ORCLPDB" - "-u" - "soe" - "-p" diff --git a/tests/dbz-twin/rac/validator.py b/tests/dbz-twin/rac/validator.py index d3a29214..d17d85c0 100644 --- a/tests/dbz-twin/rac/validator.py +++ b/tests/dbz-twin/rac/validator.py @@ -245,7 +245,7 @@ def main(): "SELECT table_name FROM olr_events WHERE event_id = ? LIMIT 1", (eid,)).fetchone() event_table = tbl_row['table_name'] if tbl_row else '?' - is_lob = event_table in LOB_TABLES + is_lob = event_table.split('.')[-1].upper() in LOB_TABLES if in_lm and not in_olr: total_missing_olr += 1 diff --git a/tests/environments/rac/.env b/tests/environments/rac/.env index ecb4a17e..ba302c8a 100644 --- a/tests/environments/rac/.env +++ b/tests/environments/rac/.env @@ -1,4 +1,4 @@ # DB_CONN is set by the rac driver (tests/sql/scripts/drivers/rac.sh) -# VM_HOST is auto-detected by vm-env.sh +# VM_HOST must be set before running RAC tests — source vm-env.sh first PDB_NAME=ORCLPDB INCLUDE_TAGS=rac diff --git a/tests/sql/scripts/drivers/rac.sh b/tests/sql/scripts/drivers/rac.sh index b8ebb41f..fb1573c7 100644 --- a/tests/sql/scripts/drivers/rac.sh +++ b/tests/sql/scripts/drivers/rac.sh @@ -36,6 +36,10 @@ DB_CONN="olr_test/olr_test@//racnodep1:1521/ORCLPDB" source "$SCRIPT_DIR/drivers/base.sh" # ---- RAC configuration ---- +if [[ -z "${VM_HOST:-}" ]]; then + # shellcheck source=/dev/null + source "$PROJECT_ROOT/tests/environments/rac/vm-env.sh" +fi VM_HOST="${VM_HOST:?VM_HOST is required — source tests/environments/rac/vm-env.sh}" VM_KEY="${VM_KEY:-$PROJECT_ROOT/oracle-rac/assets/vm-key}" VM_USER="${VM_USER:-root}"