Repository navigation
fix: don't rollback/unlock a candidate this session never locked - #10
Merged
Merged
Conversation
Config was assigned in connect() before lock() ran on both transports. If lock() raised (e.g. another session already has uncommitted changes in the shared candidate), rollback() and close() still checked only self._cfg is not None and went ahead with `rollback 0` / `unlock` for a lock we never held -- risking another operator's uncommitted edits on a real device. rustez already tracked self._locked but only consulted it in close(), not rollback(). Add self._locked to PyEZTransport, set it only after lock() succeeds, and gate rollback()/close() on it in both transports. Also move orchestrator._fetch_source's connect() inside its try/finally so a failed lock on the source device still closes the NETCONF session instead of leaking it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Config/lock state was set up inconnect()on both transports such that a failedlock()still leftrollback()/close()willing to act:self._cfgwas assigned beforelock().rollback()andclose()only checkedself._cfg is not None, so aLockErrorfromConfig.lock()still resulted inrollbackandunlockbeing sent for a candidate this session never locked.self._locked, but only consulted it inclose().rollback()still only checkedself._cfg is not None, so a failedlock()(including the new--dry-runpath added in srxsync: lock candidate config at connect() in rustez, matching PyEZ #9, since dry-run now locks) still sentrollback 0.On a real device, Junos refuses
<lock>when another session already has uncommitted changes in the shared candidate. If we ignore that failure and roll back anyway, we can discard that operator's uncommitted edits on a production firewall — a fail-closed violation.Fix:
PyEZTransport: addself._locked, set only afterConfig.lock()succeeds; gaterollback()and theunlock()call inclose()on it.RustezTransport: gaterollback()on the existingself._lockedflag (it already gatedclose()).Orchestrator._fetch_source: movet.connect()inside thetry/finallyso a failed lock on the source device still callst.close()instead of leaking the NETCONF session until GC/process exit.Test plan
test_rollback_and_close_are_noops_after_failed_locktotests/unit/test_transport_pyez.pyandtests/unit/test_transport_rustez.py: fakeConfig.lock()raises duringconnect(), then assertrollback/unlockare never called. Verified each fails against pre-fix code (git stashthe transport file, rerun) and passes with the fix.test_fetch_source_closes_session_when_lock_failstotests/unit/test_orchestrator.py: sourceconnect()raises, assertclose()still ran. Verified it fails against pre-fixorchestrator.pyand passes with the fix.ruff check .— all checks passedmypy srxsync— no issues found in 19 source filespytest tests/unit— 76 passedDid not run the
rustextra CI job locally (requires a cp312 rustez wheel not available in this environment);RustezTransportis exercised via the fake-module fixture already used bytest_transport_rustez.py, which does not need the real wheel.Confirming the device-side blast radius (whether an unlocked
<rollback>succeeds against a candidate with another session's uncommitted changes) requires a write against vsrx-ci and is flagged in the source issue for a human with lab access — not done here.🤖 Generated with Claude Code