Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/ops/li-bridge-review-comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.

# Review comment dispositions

All 62 original threads have replies with published source decisions. A fresh GraphQL readback across 47 PRs verified every expected reply, found no mismatches and found no new review threads. Resolved status alone was not accepted as proof. Re-fetch after the final publication and check the actual source/test coverage before closing the review.
All 62 original threads have replies with published source decisions. A fresh GraphQL readback across 48 PRs verified every expected reply, found no mismatches and found no new review threads. Resolved status alone was not accepted as proof. Re-fetch after the final publication and check the actual source/test coverage before closing the review.

## Later qualification findings

Expand All @@ -33,6 +33,8 @@ All 62 original threads have replies with published source decisions. A fresh Gr
| F27: interrupted deletion breaks controller startup | [596](https://github.com/linkedin/kafka/pull/596)/[597](https://github.com/linkedin/kafka/pull/597) retain all replicas, clear only marked missing-state reassignments, and finish acknowledged deletion under cleanup. Both generations pass the maintained native startup and exact-record cases; scenario revision 6 rejects missing evidence. | Scoped bundles are not full-pair qualification. |
| F28: old producer times out obtaining recreated-topic metadata | Latest PR 594 CI failure is retained. The unchanged client jar reproduces a 60-second metadata stall in a deterministic network fixture. The existing zero-expiry setting also passes a separate real-broker experiment with both survivor generations. The migration profile and deadlines are unchanged. | Client-floor decision and full final-pair qualification remain open. |

The verifier isolation finding is tracked in [598](https://github.com/linkedin/kafka/pull/598): all Gradle invocations remain single-use, and the global daemon-stop commands are removed. Its command-plan regression fails before and passes after. No functional qualification check is removed.

## PR 541

| Comment | Decision | Code/test evidence | Reply |
Expand Down
31 changes: 20 additions & 11 deletions docs/ops/li-bridge-review.md

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions docs/ops/li-bridge-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ limitations under the License.

The implementation base is Apache **3.9.2** with the reviewed LI bridge stack. Pin the final internal `3.9.2.N`, matching `3.0.1.N`, wrapper commit, archive checksums, JDKs and ZooKeeper runtime in the release record. A maintenance-baseline change requires a new qualification run; do not substitute a newer tag during rollout.

The current implementation is the split stack through `3.9-li-bridge/interrupted-deletion-recovery`, with the companion `3.0-li-bridge/interrupted-deletion-recovery` branch. It is not the closed aggregate PR 542. The canonical mergeable runbook is `docs/ops/li-bridge-upgrade.md`; the workspace copy is `LI-3.0-TO-3.9-ROLLING-UPGRADE-PLAN.md`. The `3.9-li-bridge/review-refresh` branch updates the documentation after the behavior fixes. Historical experiments are evidence, not current acceptance criteria.
The current implementation is the split stack through `3.9-li-bridge/verifier-process-isolation`, with the companion `3.0-li-bridge/interrupted-deletion-recovery` branch. It is not the closed aggregate PR 542. The canonical mergeable runbook is `docs/ops/li-bridge-upgrade.md`; the workspace copy is `LI-3.0-TO-3.9-ROLLING-UPGRADE-PLAN.md`. The `3.9-li-bridge/review-refresh` branch updates the documentation after the behavior fixes. Historical experiments are evidence, not current acceptance criteria.

**Clients do not change.** The supported producer, consumer, transactional client, Streams application, Connect worker, LI AdminClient and operational-tool artifacts/configuration must remain unchanged across every phase. Discovery must name their deployed version floor and owners. One 3.0 test archive is not proof for every externally deployed client. The latest qualification still has an unresolved cold-producer metadata timeout with an offline bootstrap broker. Record the deployed LI clients' `li.client.cluster.metadata.expire.time.ms` setting and test cold startup with unavailable bootstrap entries. Diagnostic replays and an existing config opt-out are not release approval or permission to change clients during the roll.

Expand Down Expand Up @@ -176,16 +176,16 @@ Repeat config arguments for **every** live broker. Reports distinguish `configur

## Qualification before the first canary

Run qualification against independently built archives, then repeat it against the actual published archives and official wrapper dependencies. Retain checksums, source identities, commands, phase results and logs.
Run qualification against independently built archives, then repeat it against the actual published archives and official wrapper dependencies. Retain checksums, source identities, commands, phase results and logs. Gradle commands use `--no-daemon`; the verifier must not stop unrelated builds through a shared daemon registry.

```bash
JAVA_HOME=/path/to/jdk17 \
LI_BRIDGE_JAVA_30_HOME=/path/to/jdk11 \
KAFKA_30_TGZ=/releases/kafka-30.tgz \
KAFKA_39_TGZ=/releases/kafka-39.tgz \
SKIP_LOCAL_STAGE=1 WRAPPER_ROOT=/checkouts/kafka-server \
BRIDGE_VERIFY_FULL=1 EVIDENCE_DIR=/evidence/bridge \
tests/bin/verify_li_bridge.sh
LI_BRIDGE_JAVA_30_HOME=/path/to/jdk11 \
KAFKA_30_TGZ=/releases/kafka-30.tgz \
KAFKA_39_TGZ=/releases/kafka-39.tgz \
SKIP_LOCAL_STAGE=1 WRAPPER_ROOT=/checkouts/kafka-server \
BRIDGE_VERIFY_FULL=1 EVIDENCE_DIR=/evidence/bridge \
tests/bin/verify_li_bridge.sh
```

For the protected release gate, use `tests/bin/verify_li_bridge_release.sh` with published `KAFKA_30_SHA256`, `KAFKA_39_SHA256`, approved full `KAFKA_30_COMMIT`, `KAFKA_39_COMMIT`, and `WRAPPER_COMMIT` in addition to the inputs above. It rejects dirty/unapproved checkouts, archive/source/checksum mismatches and partial verification, and runs a strict final audit.
Expand Down Expand Up @@ -240,7 +240,7 @@ Automatically stop for unexpected control versions, post-fence API 1001 traffic,

## PR inventory and merge order

All 47 open public PRs are covered below. Upgrade PRs carry `kafka-upgrade-august-2026`; CI foundations 558 and 559 do not. All current diffs are below 1,000 changed lines. These checks do not grant approval to deploy.
All 48 open public PRs are covered below. Upgrade PRs carry `kafka-upgrade-august-2026`; CI foundations 558 and 559 do not. All current diffs are below 1,000 changed lines. These checks do not grant approval to deploy.

Closed PRs 542 and 555 are superseded. GitHub automatically closed 563 and 564 during the dependency reorder because their new heads were contained in their former base branches. No release branch was merged. Their restored, separate reviews are 579 and 578.

Expand Down Expand Up @@ -293,12 +293,13 @@ Closed PRs 542 and 555 are superseded. GitHub automatically closed 563 and 564 d
| [593](https://github.com/linkedin/kafka/pull/593) | mandatory native offline-deletion checks, scenario revision 5 — verification |
| [594](https://github.com/linkedin/kafka/pull/594) | default-off placement, controller diagnostics and instrumentation audit — operations/verification |
| [597](https://github.com/linkedin/kafka/pull/597) | 3.9 interrupted deletion and mandatory scenario-revision-6 checks — controller/verification |
| [598](https://github.com/linkedin/kafka/pull/598) | verifier process isolation — verification |

Merge 558 into `3.9-li` and 559 into `3.0-li` first. They have different release bases, so do not put them in one dependent Git stack. Rebase/retarget 575 to `3.0-li` and 543 to `3.9-li`; do not merge feature work into temporary CI branches.

The GitHub stack rooted at PR 575 has the 3.0 order **575 → 541 → 577 → 583 → 585 → 588 → 592 → 595 → 596**. The stack rooted at PR 543 has the 3.9 order:

**543 → 544 → 565 → 545 → 546 → 547 → 548 → 560 → 549 → 550 → 561 → 566 → 551 → 567 → 576 → 568 → 578 → 579 → 552 → 569 → 570 → 571 → 553 → 572 → 554 → 573 → 574 → 584 → 586 → 587 → 589 → 590 → 591 → 593 → 594 → 597**.
**543 → 544 → 565 → 545 → 546 → 547 → 548 → 560 → 549 → 550 → 561 → 566 → 551 → 567 → 576 → 568 → 578 → 579 → 552 → 569 → 570 → 571 → 553 → 572 → 554 → 573 → 574 → 584 → 586 → 587 → 589 → 590 → 591 → 593 → 594 → 597 → 598**.

Retarget remaining layers after each independent merge. Wrapper `1764cc95` contains the diagnostic opt-in, ACL test repair (`6ddf2a87`) and cleanup mapping/tests (`1a9ecccf`); its source suite passes 133 tests. Add the approved wrapper/dependency/security PR and named deployment-gate owner to the release record.

Expand Down
26 changes: 13 additions & 13 deletions tests/bin/README.li-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ Use Java 17 and Python 3.9 or newer. Run the verifier from the current split-sta

```sh
JAVA_HOME=/path/to/jdk17 \
KAFKA_30_TGZ=/releases/kafka_2.12-3.0.1.83.tgz \
WRAPPER_ROOT=/checkouts/kafka-server \
EVIDENCE_DIR=/evidence/local-bridge \
tests/bin/verify_li_bridge.sh
KAFKA_30_TGZ=/releases/kafka_2.12-3.0.1.83.tgz \
WRAPPER_ROOT=/checkouts/kafka-server \
EVIDENCE_DIR=/evidence/local-bridge \
tests/bin/verify_li_bridge.sh
```

The verifier stages Scala 2.12 jars for the wrapper and builds the 3.9 archive. `LI_BRIDGE_VERSION` selects the local artifact version. Its default comes from `gradle.properties`.
Expand All @@ -37,19 +37,19 @@ Download both archives from approved releases and verify their published SHA-256

```sh
JAVA_HOME=/path/to/jdk17 \
KAFKA_30_TGZ=/releases/kafka_2.12-3.0.1.83.tgz \
KAFKA_39_TGZ=/releases/kafka_2.12-3.9.2.17.tgz \
SKIP_LOCAL_STAGE=1 \
WRAPPER_ROOT=/checkouts/kafka-server \
EVIDENCE_DIR=/evidence/released-bridge \
tests/bin/verify_li_bridge.sh
KAFKA_30_TGZ=/releases/kafka_2.12-3.0.1.83.tgz \
KAFKA_39_TGZ=/releases/kafka_2.12-3.9.2.17.tgz \
SKIP_LOCAL_STAGE=1 \
WRAPPER_ROOT=/checkouts/kafka-server \
EVIDENCE_DIR=/evidence/released-bridge \
tests/bin/verify_li_bridge.sh
```

`KAFKA_39_TGZ` disables the local archive build. It requires `SKIP_LOCAL_STAGE=1` and a 3.0 archive. The verifier checks the version inside each archive and retains the exact input bytes before compilation starts.

The wrapper's declared Kafka and Scala versions must match the 3.9 archive. Every resolved main Kafka jar must match a jar in that archive byte for byte. Test classifiers must use the selected version. Their hashes are recorded. The verifier checks the resolved files before and after wrapper testing.

Source compilation and focused source tests still run. `BRIDGE_VERIFY_FULL=1` adds the complete clients, server, and storage suites. The mixed-process test runs the retained archives.
Source compilation and focused source tests still run. `BRIDGE_VERIFY_FULL=1` adds the complete clients, server, and storage suites. The mixed-process test runs the retained archives. Gradle commands use `--no-daemon`; the verifier does not run global `--stop` commands that could interrupt unrelated builds.

## Diagnostic metrics opt-in

Expand Down Expand Up @@ -116,8 +116,8 @@ Optional scenario inputs and defaults:
To check a standalone process result, use the same scenario environment as the run:

```sh
python3 tests/bin/audit_li_bridge_evidence.py --process-only --require-archives \
--evidence-dir /evidence/process-run
python3 tests/bin/audit_li_bridge_evidence.py \
--process-only --require-archives --evidence-dir /evidence/process-run
```

This checks all phase reports, record checks, process identities and archive hashes. It does not claim that wrapper tests, full suites or release approval passed. Failures retain thread dumps and the relevant ZooKeeper state before cleanup. The process test also writes a JUnit report.
Expand Down
2 changes: 0 additions & 2 deletions tests/bin/audit_li_bridge_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@
"wrapper-tests",
"wrapper-artifacts-unchanged",
"release-39",
"stop-kafka-gradle",
"stop-wrapper-gradle",
"mixed-process",
)

Expand Down
2 changes: 0 additions & 2 deletions tests/bin/verify_li_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,6 @@ def run(self):
tasks.append("--offline")
self.command("wrapper-tests", tasks, self.wrapper, {"KAFKA_30_TGZ": str(self.legacy)})
self.wrapper_artifacts(after=True)
self.gradle("stop-kafka-gradle", ["--stop"])
self.gradle("stop-wrapper-gradle", ["--stop"], self.wrapper)
self.command("mixed-process", [self.bin / "li_bridge_mixed_cluster_smoke.sh"], extra_env={
"KAFKA_30_TGZ": str(self.legacy), "KAFKA_39_TGZ": str(self.broker),
"EVIDENCE_DIR": str(self.evidence / "mixed-process")})
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/li_bridge_commands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ def check_candidate(*args, **kwargs):
self.assertEqual(passed, json.loads((root / "verification-summary.json").read_text())["passed"])
self.assertEqual("passed" if passed else "failed(1)", verification.commands.results["evidence-audit"])

def test_verifier_uses_single_use_gradle_without_stopping_other_builds(self):
root = Path(__file__).parents[2]
with tempfile.TemporaryDirectory() as directory:
wrapper = Path(directory) / "wrapper"
wrapper.mkdir()
environment = dict(os.environ, BRIDGE_VERIFY_DRY_RUN="1", BRIDGE_VERIFY_FULL="1",
EVIDENCE_DIR=str(Path(directory) / "evidence"), WRAPPER_ROOT=str(wrapper),
KAFKA_30_TGZ=str(Path(directory) / "synthetic-unused.tgz"))
environment.pop("KAFKA_39_TGZ", None)
with mock.patch("verify_li_bridge.java_17"), mock.patch.object(Commands, "run") as commands:
Verification(root, environment).run()
gradle = [call.args[1] for call in commands.call_args_list
if Path(call.args[1][0]).name == "gradlew"]
self.assertTrue(gradle)
for command in gradle:
self.assertIn("--no-daemon", command)
self.assertNotIn("--stop", command, "Global stop can terminate an unrelated build")

def test_selection_names_tests_and_requires_all_suites(self):
path = Path(__file__).parents[1] / "bin/li_bridge_test_selection.ini"
selected = selection(path)
Expand Down
Loading