YANG Constraints Explained

YANG constraints are rules within a YANG model that automatically validate configuration data to ensure it is consistent and allowed. All YANG constraints are checked before a configuration change can take effect, preventing invalid configurations from being applied.

YANG defines three main types of constraints:

  • Must statements - to enforce arbitrary conditions (often used for referential integrity or value relationships).

  • When statements - to conditionally include or exclude parts of the data model based on other data (conditional presence of nodes).

  • Leafref types - to create references between data nodes (cross-references), ensuring that values correspond to existing nodes elsewhere.

These three types are the primary constraint mechanisms that rely on XPath, as described in RFC 7950.

Each of these plays a different role in data validation. Below are explanations of each type in detail with examples, followed by a demonstration of how they work together in a YANG model.

Note

Constraint Enforcement - YANG constraints (must/when/leafref) are enforced by NETCONF/RESTCONF servers or YANG tooling. If a constraint is violated, the configuration is rejected or an error is raised, ensuring the datastore never enters an invalid state.

Must Statements

A must statement defines an XPath expression that must evaluate to true for the data node (and its sub-tree) to be considered valid. A must statement acts as an assertion or rule that the configuration must satisfy. The must condition can refer to the node's value or other nodes in the data model.

Typical uses of must statements include:

  • Enforcing relationships between values (e.g., one value must be greater than another, or one must be set if another is set).

  • Referential integrity within a model (e.g., if a value references something, some property of that something must have a certain value).

  • Complex validation that cannot be captured by simple data types or innate constraints.

A must-stmt usually refers to descendant nodes (or self), but it can refer to any nodes. If multiple leafs are being checked (e.g., min-size <= max-size), then the must-stmt is often placed in the parent of these leafs.

A must-stmt only applies to nodes that exist. They do not apply to default leafs.

Example of a must statement:

Suppose there are two config leafs, 'min-age' and 'max-age'. To ensure that 'min-age' is always less than or equal to 'max-age', a must constraint is applied to their parent container:

container top {
  must "min-age <= max-age";

  leaf min-age {
    type uint16;
    mandatory true;
  }

  leaf max-age {
    type uint16;
    mandatory true;
  }
}

In this example, the must expression "min-age <= max-age" checks that the value of 'min-age' is less than or equal to 'max-age'. If a configuration is submitted where 'min-age' is greater than 'max-age', the must constraint will be violated and the configuration rejected.

A must constraint can also ensure referential integrity. For instance, in a networking scenario, a list of interfaces may be defined, and another leaf may reference an interface name. A must constraint can be applied to ensure that, if a certain feature is enabled, the referenced interface satisfies a required condition. (See the Combined Example below.)

Note

Multiple must statements: More than one must constraint may be applied to a node. All must expressions must evaluate to true for the node to be considered valid.

For the formal definition, see RFC 7950 Section 7.5.3.

Numeric Comparisons in XPath (String vs. Number)

XPath expressions used in YANG "must" and "when" statements operate on node-sets. Depending on the XPath type conversion rules, comparisons may be evaluated as string (lexicographic) comparisons instead of numeric comparisons.

For example, the following constraint is intended to ensure "val1 <= val2" unless either value is "0":

container mustcontainer {
  must "val1 = 0 or val2 = 0 or val1 <= val2" {
    error-message "Incorrect values!!";
  }

  leaf val1 {
    type uint32;
    units seconds;
  }

  leaf val2 {
    type uint32;
    units seconds;
  }
}

XPath node comparisons operate on node-sets and may be evaluated as string comparisons. In that case, values are compared lexicographically after the node-set is converted to a string.

For example, with val1 set to "20" and val2 set to "100", the configuration is valid numerically (20 <= 100), but the comparison may fail because "20" is greater than "100" in lexicographic order.

To compare numeric leaf or leaf-list values, force numeric conversion by using the "number()" function.

Wrong XPath

must "val1 = 0 or val2 = 0 or val1 <= val2";

Correct XPath

To force numeric comparison, wrap the operands with 'number()':

must "val1 = 0 or val2 = 0 or number(val1) <= number(val2)";

XPath: "." vs. "current()"

XPath provides multiple ways to reference the context node while an expression is evaluated.

The "." token is an abbreviated step that selects the current context node (a node-set containing the context node):

AbbreviatedStep ::= '.' | '..'

The context node changes as the expression is evaluated. For example, a new step or predicate starts a new context node.

These 2 XPath expressions can both be used to identify an interface named "eth0":

/if:interfaces/if:interface[if:name='eth0']

/if:interfaces/if:interface/if:name[.='eth0']

These 2 expressions can be used to identify the same interface entry, but they do not select the same nodes. The first expression selects the interface list entry nodes, while the second expression selects the matching interface name leaf nodes.

The context node changes as the context set changes in each step.

  1. root context set selects child node if:interfaces

  2. if:interfaces context set selects child node if:interface

etc.

The "current()" function selects the initial context node and does not change as the expression is evaluated. In YANG, this initial context is the node that contains the "must" or "when" statement.

For example, if "my-itf" is a string leaf, this must-stmt enforces that there is an interface with the same name as the value of "my-itf":

leaf my-itf {
  type string;
  must "/if:interfaces/if:interface[if:name=current()]";
}

This must expression enforces the same constraint as a leafref, which is that there must exist an interface with the same name as the value of the "my-itf" leaf.

This is equivalent to using a leafref type such as "if:interface-ref":

leaf my-itf {
  type if:interface-ref;
}

Schema Node Identifiers in Augment and Deviation Paths

A schema node identifier is a string that identifies a node in the schema tree.

Starting in release 18.10 (when the server behavior was changed to implement YANG schema paths correctly), the "choice" and "case" names must be used within "deviation" and "augment" paths.

However, the "choice" and "case" names must not appear in XPath expressions used by "must", "when", and leafref "path" statements.

For example, the following module contains "choice" and "case" statements:

container type {
  choice interface-type {
    case a {
      leaf ethernet {
        type empty;
      }
    }
    case b {
      container vlan {
        presence "example";
      }
    }
  }
}

To augment the container "vlan", this schema node identifier must be used:

augment "/type/interface-type/b/vlan" {
  // ...
}

The same requirement applies to "deviation" statements. For example:

deviation "/type/interface-type/b/vlan" {
  // ...
}

If the leaf "ethernet" or container "vlan" is referenced in a "when", "must", or leafref "path" statement, then the "choice" and "case" identifiers are not used:

typedef type-ref {
  type leafref {
    path "/type/ethernet";
  }
}

As a shorthand, the "case" statement can be omitted if the branch contains a single "anydata", "anyxml", "choice", "container", "leaf", "list", or "leaf-list" statement. In this case, the case node still exists in the schema tree, and its identifier is the same as the identifier of the child node. Schema node identifiers must still explicitly include case node identifiers.

For example:

choice interface-type {
  container ethernet {
    presence "example";
  }
}

is equivalent to:

choice interface-type {
  case ethernet {
    container ethernet {
      presence "example";
    }
  }
}

The case identifier must be unique within a choice.

As a result, if the following data model is used:

container type {
  choice interface-type {
    container vlan {
      presence "example";
    }
  }
}

The correct way to specify the target "vlan" node in an "augment" statement is:

augment "/type/interface-type/vlan/vlan" {
  // ...
}

When Statements

A when statement in YANG is used to conditionally present a data node or a set of nodes. It attaches a boolean XPath expression to a data definition (such as a leaf, container, list, or case in a choice), and the node is only present when the expression evaluates to true.

Key points about when statements:

  • A when condition does not have to be true; it only determines whether the node can appear. If the condition is false, attempts to configure the node will fail, and the server returns an error.

  • When is often used in augmentations or choice/case statements to introduce configuration that applies in specific contexts.

  • The expression in a when condition may refer to other values in the data tree.

A when-stmt MUST NOT refer to any descendant nodes or to itself. It MUST refer only to nodes outside of the node that contains the when-stmt.

Example of a when statement:

Consider a scenario where an interface has an optional 'vlan-tagging' boolean leaf that should only be present for Ethernet interface types. This can be modeled with a when condition:

leaf type {
  type identityref {
    base interface-type;
  }
}
leaf vlan-tagging {
  type boolean;
  default "false";
  when "derived-from-or-self(../type, 'ethernet')" {
    description "Present only for Ethernet interface types";
  }
}

In this example, the 'vlan-tagging' leaf is present only when the sibling 'type' is equal to or derived from the identity 'ethernet'.

Note

When vs. Must - A when condition controls the presence of a node, meaning it determines whether the node is part of the schema at a given time. A must condition, by contrast, applies to a node that is already present and checks whether a rule about its value (or other values) is satisfied. In short, when is a gating condition, while must is a validation rule.

A when-stmt applies only if its parent is present. A missing node affects the XPath result but is not itself an error.

For the formal definition, see RFC 7950 Section 7.21.5.

When vs. If-Feature

The "when" and "if-feature" statements are both used to make an object conditional, but they are very different.

The "when" statement is used to make some instances of a YANG object subtree conditional, based on values of other data in the server that can change at run-time.

container static-routes {
  when "derived-from-or-self(../type, 'rt:static')" {
    description
      "This container is only valid for the 'static' routing
       protocol.";
  }
  description
    "Configuration of the 'static' pseudo-protocol.

     Address-family-specific modules augment this node with
     their lists of routes.";
}

The YANG object "static-routes" is always present in the server object tree (if the ietf-routing module is loaded).

Data entries in the server will only have the container present if the corresponding "type" leaf is "static".

The "if-feature" statement is used to make all instances of a YANG object subtree conditional, based on boot-time parameters that cannot be changed at run-time.

leaf default-rib {
  if-feature "multiple-ribs";
  type boolean;
  default "true";
  description
    "This flag has the value of 'true' if and only if the RIB
     is the default RIB for the given address family.

     By default, control-plane protocols place their routes
     in the default RIBs.";
}

The leaf "default-rib" is only present in the server object tree if the ietf-routing module is loaded and the "multiple-ribs" feature is supported by the server.

If the feature is supported, then all instances of list "rib" will have this leaf present, or the default "true" will be used.

If the feature is not supported, then no instances of list "rib" will have this leaf present, and no default value will be used.

Leafref Type (Cross-References)

A leafref is a special YANG type used to create a reference to another leaf (or leaf-list) in the data model. Instead of defining an independent value space, a leafref reuses the value space of another node. This creates a logical link between two parts of the configuration: the leafref's value must match the value of a target leaf that exists in the data tree.

The leafref type is defined using the 'path' sub-statement, which contains an XPath expression identifying the target node in the schema tree. For example, a leafref might refer to the name of an interface in a list, or to a VLAN ID in a defined VLAN list. The value space of the leafref mirrors that of the target leaf, including any type restrictions.

By default, a leafref also requires that an actual instance of the target exists with the referenced value. This is controlled by the 'require-instance' property:

  • If 'require-instance' is "true" (default for configuration data), then the referenced value must exist. The reference must not be dangling.

  • If 'require-instance' is "false", the leafref does not require an actual target node to exist. The value is still constrained by type but may not appear in the current configuration. This mode is sometimes used in state or operational data.

Example of a leafref type:

Suppose a list of users is defined, each with a unique username. A separate leaf may reference a user by name using a leafref:

list user {
  key "username";
  leaf username {
    type string;
  }
  ...  /* other user details */
}

leaf current-operator {
  type leafref {
    path "/user/username";
    require-instance "true";
  }
  description "Username of the current operator (must exist in user list)";
}

In this example, the 'current-operator' leafref points to '/user/username'. Its value must match one of the usernames defined in the user list. With 'require-instance "true"', validation fails if the referenced username does not exist. This ensures referential integrity.

Setting 'require-instance "false"' would allow values not currently present in the user list. However, this looser reference is uncommon in configuration models and mostly applies to dynamic or state data.

Note

Leafref vs. Must for references – A leafref provides both type inheritance and referential integrity. In older models, a string leaf combined with a must statement might be used to validate external references. A leafref simplifies this by combining both checks into one construct.

For the formal definition, see RFC 7950 Section 9.9.

For practical usage with RESTCONF, yangcli-pro, and DB-API, see How to Work with Leafref Data Type.

Combined Example

Below are examples illustrating how "must", "when", and "leafref" constraints work together in a realistic YANG model scenario. The examples use the standard "ietf-interfaces" YANG module defined in RFC 7223, augmented by the module "ex-vlan.yang" from RFC 7223 Appendix C.

Initially, consider a simple scenario with two interfaces defined ("eth0" and "vlan1"):

ietf-interfaces.yang basic scenario

The module "ex-vlan.yang" augments this basic interface definition by adding a "vlan-tagging" leaf. This leaf is only present if the interface type is Ethernet ("ethernetCsmacd"):

ex-vlan.yang augment with when for vlan-tagging

This example illustrates the "when" constraint. The "vlan-tagging" leaf is conditionally added based on the interface type. If the condition is not met, the leaf is not included.

Next, the module augments the interface again to conditionally include the "base-interface" leaf if the interface type is "l2vlan":

ex-vlan.yang augment with when for base-interface

Here the "base-interface" leaf only appears when the interface type is "l2vlan".

Finally, a "must" constraint and a "leafref" are combined for the "base-interface" leaf, enforcing that the "base-interface" points to an Ethernet interface with "vlan-tagging" set to true:

ex-vlan.yang augment with leafref and must

In this augmentation:

  • The "base-interface" leaf uses a leafref to reference the "eth0" interface.

  • A "must" statement ensures that the referenced Ethernet interface has "vlan-tagging" enabled. If not, the configuration is rejected.

After applying all these constraints, the complete validated "ietf-interfaces" YANG module looks like this:

ietf-interfaces.yang final structure after constraints

The final YANG tree structure after applying all the constraints is:

+--rw interfaces
   |  +--rw interface* [name]
   |     +--rw name                        string
   |     +--rw description?                string
   |     +--rw type                        identityref
   |     +--rw enabled?                    boolean
   |     +--rw link-up-down-trap-enable?   enumeration

Via augments:
   |     +--rw vlan-tagging?               boolean
   |     +--rw base-interface?             leafref
   |     +--rw vlan-id?                    uint16

Defining Union Typedefs (Member Ordering)

Union member types are evaluated in order. If a union includes a "string" member type before more specific member types, values that could match multiple member types may match the "string" member first and never be parsed as the later member types.

For example, the XML encodings of "uint16" and "binary" (base64) are both valid strings. If "string" is placed before "uint16" and "binary", then input may be treated as "string" and will not be parsed as "uint16" or "binary".

This YANG definition is broken:

leaf name {
  type union {
    type vlan-id-type;
    type string;
    type uint16;
    type binary {
      length 7;
    }
  }
}

Always place member types in a union from most selective to least selective. Plain "string" is a catch-all that can match anything, so it is typically placed last.

Correct order for this typedef:

leaf name {
  type union {
    type vlan-id-type;
    type uint16;
    type binary {
      length 7;
    }
    type string;
  }
}

To see how YumaPro performs processing YANG Constraints compared to Open Source, check out our Transaction Performance page.