Skip to content

feat(sonic): validate BGP_NEIGHBOR_AF key references - #2619

Open
ideaship wants to merge 2 commits into
sonic-bgp-af-admin-statusfrom
sonic-validator-af-leafref
Open

feat(sonic): validate BGP_NEIGHBOR_AF key references#2619
ideaship wants to merge 2 commits into
sonic-bgp-af-admin-statusfrom
sonic-validator-af-leafref

Conversation

@ideaship

@ideaship ideaship commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2618.

Problem

BGP_NEIGHBOR_AF.neighbor is a YANG leafref into BGP_NEIGHBOR, restricted to
the same VRF by an XPath predicate:

path "../../../BGP_NEIGHBOR/BGP_NEIGHBOR_LIST[vrf_name=current()/../vrf_name]/neighbor"

sonic-bgp-neighbor.yang#L138-L146

So the vrf_name|neighbor prefix of an AF row key must name an existing
neighbor. Nothing checked that. An AF row can activate an address family for a
peer that has no BGP_NEIGHBOR entry, while the neighbor that does exist is
left with no address family at all — a session that comes up and exchanges
nothing.

Why the generated table cannot express it

_generated/_leafrefs.py is produced by
tools/sonic_yang_to_pydantic.py, whose parse_leafref_path() returns
None for relative paths and for any path containing an XPath predicate. This
one is both, so no constraint is emitted. The referring value also lives inside
a composite row key rather than a row field, which _check_leafrefs()
documents as deliberately out of scope.

KEY_PREFIX_REFS is therefore hand-maintained, and lives beside the validator
logic rather than in _generated/, which is marked do-not-edit. Each entry
records the YANG path it encodes.

Expressing the rule as a key prefix match is not a shortcut — the leafref
plus its vrf_name predicate is exactly "the first two key components must
match", so VRF scoping falls out rather than being bolted on.

Second commit

_iter_leafref_values() evaluates "|" not in row_key for single-key tables. A
non-string row key makes that membership test raise TypeError, so
validate_config() propagates an exception instead of returning a
ValidationResult — the one thing a validator should not do, since the caller
cannot then distinguish a malformed config from a broken validator. Pre-existing
and unreachable for JSON input, whose keys are always strings; reachable through
the in-memory dict the function also accepts. Split into its own commit because
it changes the behaviour of existing code.

Verification

Test-first; the tests failed in two different functions, which is what surfaced
the second bug.

Measured against the committed SONiC E2E goldens and two config_db.json taken
from a live fleet:

input AF rows flagged
osism_e2e-spine-1 golden 4 4
osism_e2e-leaf-1, osism_e2e-leaf-2 goldens 2 each 2 each
6 goldens with no BGP data 0 0
two live fleet configs 74 0

Every instance of the shape known to be broken is caught; nothing in 74 rows of
real coherent config is. 3147 passed in tests/unit (4 pre-existing xfails),
flake8 and black clean.

Scope

Nothing consumes this automatically: validate_config() is reached only through
osism sonic validate, never during a sync and not from any test or CI job. So
this changes no pipeline result — it makes the defect reportable, and gives the
keying fix something to be measured against when it comes.

🤖 Generated with Claude Code

BGP_NEIGHBOR_AF.neighbor is a YANG leafref into BGP_NEIGHBOR restricted
to the same VRF, so the vrf_name|neighbor prefix of an AF row key must
name an existing neighbor. Nothing checked that. An AF row could
activate an address family for a peer with no BGP_NEIGHBOR entry, while
the neighbor that does exist was left with no address family at all --
a session that comes up and exchanges nothing.

The generated constraint table cannot express this. Its leafref path is
both relative and predicated:

    ../../../BGP_NEIGHBOR/BGP_NEIGHBOR_LIST[vrf_name=current()/../vrf_name]/neighbor

and parse_leafref_path() in tools/sonic_yang_to_pydantic.py returns None
for both shapes, so the generator emits no constraint. The referring
value also lives inside a composite row key rather than a row field,
which the generated checker documents as out of scope. KEY_PREFIX_REFS
is therefore hand-maintained and lives beside the validator logic rather
than in _generated/, which is marked do-not-edit.

Rows are skipped when their key has too few components, or when the key
is not a string: both are malformed rows that the row schema already
reports, and reporting them here too would turn one defect into two.

Checked against the committed SONiC E2E goldens and two configs taken
from a live fleet: all 8 mismatched address-family rows in the goldens
are flagged, and none of the 74 rows in the fleet configs are, so the
check separates the two shapes it is meant to distinguish.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Roger Luethi <luethi@osism.tech>
_iter_leafref_values() tests "|" not in row_key for tables whose list has
a single key. A non-string row key makes that membership test raise
TypeError, so validate_config() propagates an exception instead of
returning a ValidationResult -- the one thing a validator should never
do, since the caller cannot tell a malformed config from a broken
validator.

JSON object keys are always strings, so this is out of reach for a config
read from a file. It is reachable through the in-memory dict the function
also accepts, and through any loader that produces non-string keys.

Guard only the membership test, so rows that do carry the field are
unaffected. The malformed key itself is left to the row schema, which
already reports it.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Roger Luethi <luethi@osism.tech>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants