[202412] Remove skip-sort bypass and backport GCU patch sorter crash fix (#4668) - #437
Draft
rimunagala wants to merge 3 commits into
Draft
[202412] Remove skip-sort bypass and backport GCU patch sorter crash fix (#4668)#437rimunagala wants to merge 3 commits into
rimunagala wants to merge 3 commits into
Conversation
Removes the skip_sort_tables mechanism added by Azure#293, Azure#300 and Azure#305 so that every patch goes through the normal sorting path. The bypass was an interim mitigation from March/April 2026, added while GCU apply-patch was too slow for the Fairwater DACL scenario. The underlying performance work has since landed on this branch: sonic-net/sonic-utilities#3831 (via Azure#254) and sonic-net/sonic-utilities#4310, #4476, #4478 and #4554 (via Azure#352). Beyond simply being redundant, the bypass actively obscures the signal we need. While it is in place, any patch whose operations all match /ACL_TABLE/FAIRWATER_DACL_MITIGATION*/ports never reaches the sorter, so healthy apply times on those patches cannot be used as evidence that sorting performance is fixed. Removed: - the skip-sort block and `import fnmatch` in generic_updater.py - generic_config_updater/skip_sort_tables.txt - the corresponding setup.py package_data entry - the three unit tests covering the bypass, and their helper Deliberately kept: `import jsonpatch` and `JsonChange`, which Azure#293 also added. These are not part of the bypass. The `else:` branch of `if sort:` already referenced both symbols without importing them, so the sort=False path carried a latent NameError that Azure#293 incidentally fixed. Removing them would reintroduce that bug. Verified: generic_updater.py now differs from its pre-Azure#293 state by exactly those two imports and nothing else. Signed-off-by: rimunagala <rimunagala@microsoft.com>
Backport of sonic-net/sonic-utilities#4668, originally authored by Brad House - Nexthop <bhouse@nexthop.ai>, cherry-picked from upstream commit 0552d0d24f67b287d74ef82ef7922f389f715c5f. Applied cleanly with no conflicts. The patch sorter aborted with an unhandled ValueError ("'<x>' is not in list") when a patch changed a create-only PORT field (for example `lanes` during a breakout) while that port was a member of a multi-member leaf-list such as ACL_TABLE.ports. During move validation the sorter transiently removes the port from the leaf-list in the simulated intermediate config, and a still-present leafref to it then fails to resolve. The exception escaped RemoveCreateOnlyDependencyMoveValidator._validate_member and aborted the entire sort, failing the apply and triggering auto-rollback. The fix treats an unresolvable reference in a simulated intermediate config as an invalid move: the error is caught in _validate_member and False is returned, so the DFS backtracks to a valid ordering instead of aborting. This is needed on 202412 because the preceding commit removes the skip-sort bypass, which means ACL_TABLE.ports patches now go through the sorter rather than around it. ACL_TABLE.ports is the exact leaf-list named in the upstream report, so without this fix that newly-enabled code path carries a known crash. Includes the upstream regression test: pytest tests/generic_config_updater/patch_sorter_test.py \ -k test_validate__unresolvable_ref_in_simulated_config__move_rejected Co-authored-by: Brad House - Nexthop <bhouse@nexthop.ai> Signed-off-by: rimunagala <rimunagala@microsoft.com>
The backport in the preceding commit applied cleanly at the text level, but none
of its five new tests could run on this branch. Two API differences exist between
upstream master and 202412, both confined to the tests.
1. JsonMoveGroup signature
TypeError: JsonMoveGroup.__init__() takes from 1 to 2 positional arguments but 3 were given
Upstream's JsonMoveGroup takes a leading name argument, so the upstream tests
construct it as JsonMoveGroup("", move). On 202412 the signature is still
JsonMoveGroup(move: JsonMove = None). Dropped the empty name argument at the
five new call sites.
2. Validator return type
TypeError: 'bool' object is not subscriptable
Upstream's MoveValidator.validate() returns a Tuple[bool, Optional[str]], so
the upstream tests assert on validate(...)[0]. On 202412 validate() returns a
plain bool. Dropped the [0] subscript at the four affected assertions.
Both forms now match how every pre-existing test in this file already calls
JsonMoveGroup and validate().
The production hunks of #4668 needed no adaptation: they reference JsonMoveGroup
only in type annotations and do not depend on the validate() return shape.
Verified in a container with the 202412 GCU test dependencies:
- the five #4668 tests: 5 passed
- full tests/generic_config_updater suite, 202412 baseline (4c36b19):
63 failed, 479 passed
- full suite with these three commits applied:
62 failed, 482 passed
- regressions introduced: 0
The failure count drops by one because the removed skip-sort test
test_apply__all_ops_match_skip_sort__sort_skipped was already failing on the
202412 baseline. The remaining 62 failures are pre-existing on 202412 and
unrelated to these commits.
Signed-off-by: rimunagala <rimunagala@microsoft.com>
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
1 task
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.
Why I did it
The
skip_sort_tablesbypass was added to 202412 in March/April 2026 (#293, #300, #305) as aninterim mitigation, at a time when GCU
apply-patchwas too slow for the Fairwater DACL scenarioand was tripping Hw-proxy inband / NDM WCF timeouts.
That mitigation has since been overtaken by the actual performance work, which is now on this
branch:
sonic-net/sonic-utilities#3831— GCU performance enhancementssonic-net/sonic-utilities#4310— GCU wheelsonic-net/sonic-utilities#4476— cacheloadData()callssonic-net/sonic-utilities#4478— batch leaf-list changes into a single REPLACE movesonic-net/sonic-utilities#4554— initSonicDBConfigfor multi-ASICBeyond being redundant, the bypass actively suppresses the signal we need. While it is in place,
any patch whose operations all match
/ACL_TABLE/FAIRWATER_DACL_MITIGATION*/portsneverreaches the sorter at all — so a healthy apply time on those patches cannot be used as evidence
that sorting performance is fixed. Removing it is a prerequisite for producing that evidence.
How I did it
Commit 1 — remove the skip-sort bypass
import fnmatchfromgeneric_updater.pygeneric_config_updater/skip_sort_tables.txtsetup.pypackage_dataentryimport jsonpatchandJsonChangeare deliberately kept, even though #293 added themalongside the bypass. They are not part of it: the
else:branch ofif sort:already referencedboth symbols without importing them, so the
sort=Falsepath carried a latentNameErrorthat#293 incidentally fixed. Removing them would reintroduce that bug.
Commit 2 — backport
sonic-net/sonic-utilities#4668The patch sorter aborted with an unhandled
ValueError ("'<x>' is not in list")when a patchchanged a create-only PORT field (e.g.
lanesduring a breakout) while that port was a member ofa multi-member leaf-list such as
ACL_TABLE.ports. The exception escapedRemoveCreateOnlyDependencyMoveValidator._validate_memberand aborted the whole sort, failing theapply and triggering auto-rollback. The fix treats an unresolvable reference in a simulated
intermediate config as an invalid move, so the DFS backtracks instead of aborting.
This is required here rather than optional: commit 1 routes
ACL_TABLE.portspatches into thesorter, and
ACL_TABLE.portsis the exact leaf-list named in the upstream report. Without it, thenewly-enabled code path carries a known crash.
Cherry-picked cleanly from upstream
0552d0d24f67b287d74ef82ef7922f389f715c5fwith no conflicts.Re-authored to match the backport convention used by #352; original author credited via
Co-authored-by.Commit 3 — adapt #4668's regression tests to the 202412 API
Commit 2 applied cleanly at the text level, but that turned out to be misleading: none of the five
tests it adds could actually execute on this branch. Two API differences exist between upstream
master and 202412, and git's 3-way merge did not surface either of them because the differing
signatures appear only as unchanged context lines.
JsonMoveGroup.__init__nameargument(move: JsonMove = None)TypeError: takes from 1 to 2 positional arguments but 3 were givenMoveValidator.validate()Tuple[bool, Optional[str]]boolTypeError: 'bool' object is not subscriptableCommit 3 drops the empty
nameargument at the five newJsonMoveGroup(...)call sites, and the[0]subscript at the four affectedvalidate(...)assertions. Both now match how everypre-existing test in this file already calls those APIs.
The production hunks of #4668 required no adaptation — they reference
JsonMoveGrouponly intype annotations, and do not depend on the
validate()return shape. The behaviour beingbackported is unchanged from upstream.
How to verify it
This repository does not run unit tests in CI (the required checks are CodeQL, Semgrep and CLA),
so the suite was run manually in a container carrying the 202412 GCU test dependencies
(libyang 1.0.73,
click==7.0,swsssdk,mockredispy,pyfakefs,responses,deepdiff).4c36b195)Regressions introduced: 0. All five of #4668's tests pass.
The failure count decreases by one because
test_apply__all_ops_match_skip_sort__sort_skippedwasalready failing on the 202412 baseline, and commit 1 deletes it. See the note below — that test
never actually worked.
Every one of the 63 baseline failures was traced to root cause rather than assumed to be noise.
They fall into four groups, none of which moves in either direction as a result of these commits:
TestSortAlgorithmFactorygenerator classificationmain_testexpects a--timeoptionmain.pydoes not definemock_openin the skip-sort testlibyang (57). The
sonic-yang-mgmtinstalled in my test image doesimport libyang as ly(CESNET
python3-libyang3.1.0), sosy.rootis alibyang.data.DContainer. 202412'sgu_common.pydoesimport yang as lyand callssy.root.tree_for()andsy.root.find_path(xpath).data()— the older binding's API — givingAttributeError: 'DContainer' object has no attribute 'tree_for'. The image happens to carry bothstacks (
python3-yang1.0.73 providing moduleyang, andpython3-libyang3.1.0 providing modulelibyang). This is the skew that upstream #4118 ("remove direct dependency on libyang") resolves,and #4118 is deliberately not part of this PR. A sonic-slave 202412 environment pins the older
pair, so these should not appear there. Purely an artefact of my environment; unrelated to this
change.
Generator classification (3).
patch_sorter_test.pylistsRemoveCreateOnlyDependencyMoveGeneratorunderexpected_generators, whilepatch_sorter.pyregisters it under
move_non_extendable_generators. The test file and the source disagree on thisbranch today.
--time(2).main_test.pycontainstest_list_checkpoints_with_timeand assertsargs.time,but the
list-checkpointssubparser inmain.pydefines only-v/--verbose, so argparse exitswith
unrecognized arguments: --time. The test file is ahead of the source on this branch.Both of the above look like test files being taken from a newer revision than the corresponding
source change. They are untouched by this PR and are left as-is rather than fixed here, to keep the
diff to the stated scope. Happy to file them separately if useful.
Broken skip-sort mock (1) — worth calling out. The test removed by commit 1 did this:
This copies only
side_effectfrom the helper and discards itsreturn_value, which is theconfigured file handle. The patched
open()therefore returns a bareMagicMock, whose default__iter__is empty, soevaluates to
[], the bypass is never entered, andsortis called — failing the assertion.Confirmed directly: reproducing the pattern as written yields
[], whereaspatch("builtins.open", new_callable=mock_open, read_data=...)yields the expected entry.The same flaw makes the sibling test
test_apply__no_ops_match_skip_sort__sort_not_skippedpassvacuously — an empty skip list means sorting happens, which is exactly what that test asserts.
So the skip-sort feature has had no effective unit-test coverage since it was introduced, which went
unnoticed because this repository does not run the unit tests in CI. Commit 1 removes both tests
along with the feature, so nothing further is needed here; noting it only because it is relevant to
how much confidence the previous green-looking state deserved.
Other checks performed locally:
skip_sort/skip-sortreference anywhere in the treegeneric_updater.pynow differs from its pre-[generic_config_updater]: Skip patch sorting for ACL_TABLE/DENY_NEW_INGRESS_TABLE #293 state by exactly the two intentionallyretained imports and nothing else
deletions, 0 insertions
py_compileclean;flake8code profile unchanged from the 202412 baseline(
generic_updater.py16/16,generic_updater_test.py30/30,patch_sorter.py72/72)Rollout note
Merging this removes the mitigation from every subsequent 202412 image build, not just from
piloted devices. The intent is to build a GCU addon container from this branch and pilot it on a
small number of
20241212.61devices with the MRC isolation scenario owners before it reacheswider fleet use.
Not included
sonic-net/sonic-utilities#4335(suboptimal plan for CreateOnly paths) — deliberately deferred.It is +74/-40 in
patch_sorter.pyand rewrites ~135 lines of expected-plan test fixtures, i.e.it intentionally changes generated plans. That deserves its own validation cycle rather than
riding along here. Cherry-pick conflicts are small (3 hunks / 29 lines) if we decide to take it.
sonic-net/sonic-utilities#4118(remove direct libyang dependency) — dependency refactor,unrelated to this change.
Which release branch to port