skip to content

In a YANG must expression on a BGP neighbour list entry, what is the XPath context node, and why does a predicate need current()?

level: seniorimportance: nice to knowfreq 6%

answer

  1. where relative paths start
  2. predicates change the context
  3. the initial context node
  4. config sees configuration only

basics

~20 s

A YANG must's context node is the instance of the node carrying it, where relative paths start. Inside a predicate the context moves to each filtered node; current() returns the original node, so the predicate can use the neighbour's own leafs.

solid answer

~50 s

RFC 7950 §7.5.3 makes the context node of a `must` on a data node the instance being checked — here, each `neighbor` entry; in `input`, `output` or a `notification` it is the operation or notification node. For configuration, the accessible tree is the datastore holding that node, so state data is invisible. A predicate is evaluated with each filtered node as its context, and `current()` returns the initial context node. In `../peer-group[name = current()/peer-group]/peer-as`, `current()/peer-group` is the neighbour's own group name; without `current()`, `peer-group` would be looked up under each peer-group entry, find nothing, and reject every neighbour relying on its group. The `must` sits on the entry, not on `peer-as`, because a `must` on an absent leaf never runs. A `when` on a data node instead sees a dummy node standing in for it.

code

yang · 19 lines
yang
container bgp {
  list peer-group {
    key "name";
    leaf name { type string; }
    leaf peer-as { type inet:as-number; }
  }
  list neighbor {
    key "address";
    must "peer-as or ../peer-group[name = current()/peer-group]/peer-as" {
      error-message "neighbor needs a peer-as, directly or via its peer-group";
      error-app-tag "peer-as-missing";
    }
    leaf address { type inet:ip-address; }
    leaf peer-as { type inet:as-number; }
    leaf peer-group {
      type leafref { path "../../peer-group/name"; }
    }
  }
}

go deeper

for a junior

Recall that a must is evaluated starting from the node it is written on, so a relative path like ../peer-group begins there.

for a middle

Explain how a predicate shifts the context to each filtered node and how current() reaches back to the node the expression started from.

for a senior

Debug real expressions: put a must where an instance always exists, never reach for state from configuration, and trace predicates node by node before trusting them.

for a principal

Keep cross-node rules readable and portable: document grouping contexts, avoid document-order functions and 64-bit equality, and prefer declarative statements where they suffice.

## The context node, statement by statement An XPath expression is evaluated relative to a **context node**: a relative path such as `../peer-group` starts there. RFC 7950 fixes the context node for each place a YANG expression can appear: | Statement and position | Context node | |---|---| | `must` on a data node (container, list, leaf …) | the instance of that node being checked | | `must` in an `input` or `output` | the node representing the RPC or action | | `must` in a `notification` | the node representing the notification | | `when` on a data node | a dummy node with the same name but no value and no children, standing in for the node | | `when` on `uses`, `choice` or `case` | the closest ancestor that is a data node | A `when` attached to an `augment` has its own rule, which belongs with augmentation. Two consequences matter in practice. A `must` runs once for each existing instance of its node, so a `must` on a leaf that is absent never runs at all. And because a `when` on a data node sees only a placeholder for that node, RFC 9907 says its expression should not reference the context node or its descendants. ## The accessible tree The context node sits inside an **accessible tree**, the data the expression is allowed to see (RFC 7950 §6.4.1): - for a constraint on configuration data: the datastore in which the context node exists — configuration only; - for a constraint on state data: all state data in the server plus the running configuration; - for a notification or for operation input and output: that instance, all state data and the running configuration. In every case, leafs and leaf-lists whose default values are in use exist in the accessible tree, and a non-presence container exists wherever its parent does. Unprefixed names belong to the namespace of the current node — inside a grouping, of the place where the grouping is used — and there are no variable bindings. The function library is XPath 1.0's core plus YANG's own: `current()`, `re-match()`, `deref()`, `derived-from()`, `derived-from-or-self()`, `enum-value()` and `bit-is-set()`. A configuration `must` therefore cannot test operational state, such as whether a BGP session or an interface is up: that data is not in its tree, so the path selects nothing. RFC 8342 (NMDA) explains why this is deliberate: configuration validity cannot depend on the state of resources, or removing a resource or rebooting would leave stored configuration invalid. ## current() inside a predicate A predicate `[...]` is evaluated once for each node it filters, with that node as its own context. `current()` (RFC 7950 §10.1.1) returns a node set whose only member is the *initial* context node — the one the whole expression started from. YANG's XPath context has no variable bindings, so `current()` is how a predicate refers back to that node. ## The BGP neighbour, end to end The code example lets a neighbour take its remote AS either directly or from its peer group, and puts the `must` on the `neighbor` list entry. Evaluate it for neighbour 198.51.100.7, which names peer group `transit` and has no `peer-as` of its own, while group `transit` has `peer-as 64496`: 1. The context node is the neighbour entry; `peer-as` selects nothing, so the left side of `or` is false. 2. `../peer-group` moves up to the `bgp` container and selects every peer-group entry. 3. For each entry, the predicate compares its `name` with `current()/peer-group` — the neighbour's own `peer-group` leaf, `transit`. Only the `transit` entry survives. 4. `/peer-as` on that entry exists, the node set is non-empty, and the `must` is true. Write the predicate as `[name = peer-group]` and step 3 looks for a `peer-group` child of each peer-group entry instead. There is none, every comparison is false, and every neighbour that relies on its group is rejected with `peer-as-missing`. Moving the `must` onto the `peer-as` leaf fails the other way: a neighbour with no `peer-as` has no instance to evaluate, so nothing is rejected. That is also why `mandatory true` cannot express this rule — it cannot accept a value inherited from elsewhere. ## Traps - **64-bit numbers.** XPath numbers are IEEE 754 doubles; RFC 7950 warns that some `int64`, `uint64` and `decimal64` values cannot be represented exactly, so equality comparisons on them can surprise. - **Document order.** The data tree has no defined document order. RFC 9907 says `position()`, `last()` and the sibling axes should not be used except where the context is a user-ordered list or leaf-list, and the preceding and following axes only where document order cannot change the result. - **Identities.** For identityref values, RFC 9907 prefers `derived-from-or-self()` to string equality, so later derived identities still match. - **Groupings.** An expression inside a grouping is evaluated where the grouping is used; RFC 9907 asks authors to document the context it expects.

  • Why can't a YANG must on a configuration leaf require the referenced interface to be operationally up?
    For configuration, RFC 7950 makes the accessible tree the datastore that holds the context node, so operational state is simply not there and the path selects nothing. RFC 8342 explains the design: configuration validity must not depend on the state of resources, or unplugging a module or rebooting would leave stored configuration invalid.
  • What is the context node for a when on the ebgp-multihop leaf itself, and what follows from it?
    RFC 7950 replaces the leaf's instances with a single dummy node of the same name, with no value and no children, and uses that as the context. So `../peer-type` reaches the neighbour entry, but the leaf's own value is not available; RFC 9907 accordingly says a `when` should not reference its context node or descendants.

saying these in an interview costs you the question

  • Inside a predicate, relative paths still start from the must's own node.
  • A must on the peer-as leaf will catch a neighbour that omits peer-as.
  • A configuration must can check operational state such as a session being up.
  • current() returns the node the predicate is currently testing.
  • Leafs left at their default value are invisible to must expressions.