Templates
Simplify change management in your network using templates.
NSO comes with a flexible and powerful built-in templating engine, which is based on XML. The templating system simplifies how you apply configuration changes across devices of different types and provides additional validation against the target data model. Templates are a convenient, declarative way of updating structured configuration data and allow you to avoid lots of boilerplate code.
You will most often find this type of configuration templates used in services, which is why they are sometimes also called service templates. However, we mostly refer to them simply as XML templates, since they are defined in XML files.
NSO loads templates as part of a package, looking for XML files in the templates
subdirectory. You then apply an XML template through API or by connecting it with a service through a service point, allowing NSO to use it whenever a service instance needs updating.
XML templates are distinct from so-called “device templates”, which are dynamically created and applied as needed by the operator, for example in the CLI. There are also other types of templates in NSO, unrelated to XML templates described here.
Structure of a Template
Template is an XML file with the config-template
root element, residing in the http://tail-f.com/ns/config/1.0
namespace. The root contains configuration elements according to NSO YANG schema and XML processing instructions.
Configuration element structure is very much like the one you would find in a NETCONF message since it uses the same encoding rules defined by YANG. Additionally, each element can specify a tags
attribute that refines how the configuration is applied.
A typical template for configuring an NSO-managed device is:
The first line defines the root node. It contains elements that follow the same structure as that used by the CDB, in particular, the devices device``
name
``config
path in the CLI. In the printout, two elements, device
and config
, also have a tags
attribute.
You can write this structure by studying the YANG schema if you wish. However, a more typical approach is to start with manipulating NSO configuration by hand, such as through the NSO CLI or web UI. Then generate the XML structure with the help of NSO output filters. You can use commit dry-run outformat xml or show ... | display xml
commands, or even the ncs_load
utility. For a worked, step-by-step example, refer to the section A Template is All You Need.
Having the basic structure in place, you can then fine-tune the template by adding different processing instructions and tags, as well as replacing static values with variable references using the XPath syntax.
Note that a single template can configure multiple devices of different type, services, or any other configurable data in NSO; basically the same as you can do in a CLI commit. But a single, gigantic template can become a burden to maintain. That is why many developers prefer to split up bigger configurations into multiple feature templates, either by functionality or by device type.
Finally, the name of the file, without the .xml
extension is the name of the template. The name allows you to reference the template from the code later on. Since all the template names reside in the same namespace, it is a good practice to use a common naming scheme, preferably <package name>
-
<feature>
.xml
to ensure template names are unique.
Other Ways to Generate the XML Template Structure
The NSO CLI features a templatize command that allows you to analyze a given configuration and find common configuration patterns. You can use these to, for example, create a configuration template for a service.
Suppose you have an existing interface configuration on a device:
Using the templatize
command, you can search for patterns in this part of the configuration, which produces the following:
In this case, NSO finds a single pattern (the only one) and creates the corresponding template. In general, NSO might produce a number of templates. As an example, try running the command within the examples.ncs/implement-a-service/dns-v3
environment.
The algorithm works by searching the data at the specified path. For any list it encounters, it compares every item in the list with its siblings. If the two items have the same structure but not necessarily the same actual values (for leafs), that part of the configuration can be made into a template. If the two list items use the same value for a leaf, the value is used directly in the generated template. Otherwise, a unique variable name is created and used in its place, as shown in the example.
However, templatize
requires you to reference existing configurations in NSO. If such configuration is not readily available to you and you want to avoid manually creating sample configuration in NSO first, you can use the sample-xml-skeleton functionality of the yanger utility to generate sample XML data directly:
You can replace the value of --sample-xml-skeleton-path
with the path to the part of the configuration you want to generate.
In case the target data model contains submodules, or references other non-built-in modules, you must also tell yanger
where to find additional modules with the -p
parameter, such as adding -p src/yang/
to the invocation.
Values in a Template
Some XML elements, notably those that represent leafs or leaf-lists, specify element text content as values that you wish to configure, such as:
NSO converts the string value to the actual value type of the YANG model automatically when the template is applied.
Along with hard-coded, static content (rtr01
), the value may also contain curly brackets ({...}
), which the templating engine treats as XPath 1.0 expressions.
The simplest form of an XPath expression is a plain XPath variable:
A value can contain any number of {...}
expressions and strings. The end result is the concatenation of all the strings and XPath expressions. For example, <description>Link to PE: {$PE} - {$PE_INT_NAME}</description>
might evaluate to <description>Link to PE: pe0 - GigabitEthernet0/0/0/3</description>
.
if you set PE
to pe0
and PE_INT_NAME
to GigabitEthernet0/0/0/3
when applying the template.
You set the values for variables in the code where you apply the template. NSO also sets some predefined variables, which you can reference:
$DEVICE
: The name of the current device. Cannot be overridden.$TEMPLATE_NAME
: The name of the current template. Cannot be overridden.$SCHEMA_OPAQUE
: Defined if the template is registered for a servicepoint (the top node in the template hasservicepoint
attribute) and the correspondingncs:servicepoint
statement in the YANG model hastailf:opaque
substatement. Set to the value of thetailf:opaque
statement.$OPERATION
: Defined if the template is registered for a servicepoint with thecbtype
attribute set topre-/post-modification
(see Service Callpoints and Templates). Contains the requested service operation; create, update, or delete.
The {...}
expression can also be any other valid XPath 1.0 expression. To address a reachable node, you might for example use:
Or to select a leaf node, device
:
NSO then uses the value of this leaf, say ce5
, when constructing the value of the expression.
However, there are some special cases. If the result of the expression is a node-set (e.g. multiple leafs), and the target is a leaf list or a list's key leaf, the template configures multiple destination nodes. This handling allows you to set multiple values for a leaf list or set multiple list items.
Similarly, if the result is an empty node set, nothing is set (the set operation is ignored).
Finally, what nodes are reachable in the XPath expression, and how, depends on the root node and context used in the template. See XPath Context in Templates.
Conditional Statements
The if
, and the accompanying elif
, else
, processing instructions make it possible to apply parts of the template, based on a condition. For example:
The preceding template shows how to produce different configuration, for network bandwidth management in this case, when different qos-class/priority
values are specified.
In particular, the sub-tree containing the priority-realtime
tag will only be evaluated if qos-class/priority
in the if
processing instruction evaluates to the string 'realtime'
.
The subtree under the elif
processing instruction will be executed if the preceding if
expression evaluated to false
, i.e. qos-class/priority
is not equal to the string 'realtime'
, but 'critical'
instead.
The subtree under the else
processing instruction will be executed when both the preceding if
and elif
expressions evaluated to false
, i.e. qos-class/priority
is not 'realtime'
nor 'critical'
.
In your own code you can of course use just a subset of these instructions, such as a simple if
- end
conditional evaluation. But note that every conditional evaluation must end with the end
processing instruction, to allow nesting multiple conditionals.
The evaluation of the XPath statements used in the if
and elif
processing instructions follow the XPath standard for computing boolean values. In summary, the conditional expression will evaluate to false when:
The argument evaluates to an empty node-set.
The value of the argument is either an empty string or numeric zero.
The argument is of boolean type and evaluates to false, such as using the
not(true())
function.
Loop Statements
The foreach
and for
processing instructions allow you to avoid needless repetition: they iterate over a set of values and apply statements in a sub-tree several times. For example:
The printout shows the use of foreach
to configure a set of IP routes (the list ip-route-forwarding-list
) for a Cisco network router. If there is a tunnel
list in the service model, the {/tunnel}
expression selects all the items from the list. If this is a non-empty set, then the sub-tree containing ip-route-forwarding-list
is evaluated once for every item in that node set.
For each iteration, the initial context is set to one node, that is, the node being processed in that iteration. The XPath function current()
retrieves this initial context if needed. Using the context, you can access the node data with relative XPath paths, e.g. the {network}
code in the example refers to /tunnel[...]/network
for the current item.
foreach
only supports a single XPath expression as its argument and the result needs to be a node-set, not a simple value. However, you may use XPath union operator to join multiple node sets in a single expression when required: {some-list-1 | some-leaf-list-2}
.
Similarly, for
is a processing instruction that uses a variable to control the iteration, in line with traditional programming languages. For example, the following template disables the first four (0-3) interfaces on a Cisco router:
In this example, three semicolon-separated clauses follow the for
keyword:
The first clause is the initial step executed before the loop is entered the first time. The format of the clause is that of a variable name followed by an equals sign and an expression. The latter may combine literal strings and XPath expressions surrounded by
{}
. The expression is evaluated in the same way as the XML tag contents in templates. This clause is optional.The second clause is the progress condition. The loop will execute as long as this condition evaluates to true, using the same rules as the
if
processing instruction. The format of this clause is an XPath expression surrounded by{}
. This clause is mandatory.The third clause is executed after each iteration. It has the same format as the first clause (variable assignment) and is optional.
The foreach
and for
expressions make the loop explicit, which is why they are the first choice for most programmers. Alternatively, under certain circumstances, the template invokes an implicit loop, as described in XPath Context in Templates.
Template Operations
The most common use-case for templates is to produce new configuration but other behavior is possible too. This is accomplished by setting the tags
attribute on XML elements.
NSO supports the following tags
values, colloquially referred to as “tags”:
merge
: Merge with a node if it exists, otherwise create the node. This is the default operation if no operation is explicitly set.replace
: Replace a node if it exists, otherwise create the node.create
: Creates a node. The node must not already exist. An error is raised if the node exists.nocreate
: Merge with a node if it exists. If it does not exist, it will not be created.delete
: Delete the node.
Tags merge
and nocreate
are inherited to their sub-nodes until a new tag is introduced.
Tags create
and replace
are not inherited and only apply to the node they are specified on. Children of the nodes with create
or replace
tags have merge
behavior.
Tag delete
applies only to the current node; any children (except keys specifying the list/leaf-list entry to delete) are ignored.
Operations on Ordered Lists and Leaf-lists
For ordered-by-user lists and leaf lists, where item order is significant, you can use the insert
attribute to specify where in the list, or leaf-list, the node should be inserted. You specify whether the node should be inserted first or last in the node-set, or before or after a specific instance.
For example, if you have a list of rules, such as ACLs, you may need to ensure a particular order:
However, it is not uncommon that there are multiple services managing the same ordered-by user list or leaf-list. The relative order of elements inserted by these services might not matter, but there are some constraints on element positions that need to be fulfilled.
Following the ACL rules example, suppose that initially the list contains only the "deny-all" rule:
There are services that prepend permit rules to the beginning of the list using the insert="first"
operation. If there are two services creating one entry each, say 10.0.0.0/8 and 192.168.0.0/24 respectively, then the resulting configuration looks like this:
Note that the rule for the second service comes first because it was configured last and inserted as the first item in the list.
If you now try to check-sync the first service (10.0.0.0/8), it will report as out-of-sync, and re-deploying it would move the 10.0.0.0/8 rule first. But what you really want is to ensure the deny-all rule comes last. This is when the guard
attribute comes in handy.
If both the insert
and guard
attributes are specified on a list entry in a template, then the template engine first checks whether the list entry already exists in the resulting configuration between the target position (as indicated by the insert
attribute) and the position of an element indicated by the guard
attribute:
If the element exists and fulfills this constraint, then its position is preserved. If a template list entry results in multiple configuration list entries, then all of them need to exist in the configuration in the same order as calculated by the template, and all of them need to fulfill the guard constraint in order for their position to be preserved.
If the list entry/entries do not exist, are not in the same order, or do not fulfill the constraint, then the list is reordered as instructed by the insert statement.
So, in the ACL example, the template can specify the guard as follows:
A guard can be specified literally (e.g. guard="deny-all"
if "name" is the key of the list) or using an XPath expression (e.g. guard="{$LASTRULE}"
). If the guard evaluates to a node-set consisting of multiple elements, then only the first element in this node-set is considered as the guard. The constraint defined by the guard
is evaluated as follows:
If the guard evaluates to an empty node-set (i.e. the node indicated by the guard does not exist in the target configuration), then the constraint is not fulfilled.
If
insert="first"
, then the constraint is fulfilled if the element exists in the configuration before the element indicated by the guard.If
insert="last"
, then the constraint is fulfilled if the element exists in the configuration after the element indicated by the guard.If
insert="after"
, then the constraint is fulfilled if the element exists in the configuration before the element indicated by theguard
, but after the element indicated by thevalue
attribute.If
insert="before"
, then the constraint is fulfilled if the element exists in the configuration after the element indicated by theguard
, but before the element indicated by the orvalue
attribute.
Macros in Templates
Templates support macros - named XML snippets that facilitate reuse and simplify complex templates. When you call a previously defined macro, the templating engine inserts the macro data, expanded with the values of the supplied arguments. The following example demonstrates the use of a macro.
When using macros, be mindful of the following:
A macro must be a valid chunk of XML, or a simple string without any XML markup. So, a macro cannot contain only start-tags or only end-tags, for example.
Each macro is defined between the
<?macro?>
and<?endmacro?>
processing instructions, immediately following the<config-template>
tag in the template.A macro definition takes a name and an optional list of parameters. Each parameter may define a default value.
In the preceding example, a macro is defined as:\
Here,
GbEth
is the name of the macro. This macro takes three parameters,name
,ip
, andmask
. The parametersname
andmask
have default values, andip
does not.The default value for
mask
is a fixed string, while the one forname
by default gets its value through an XPath expression.A macro can be expanded in another location in the template using the
<?expand?>
processing instruction. As shown in the example (line 29), the<?expand?>
instruction takes the name of the macro to expand, and an optional list of parameters and their values.The parameters in the macro definition are replaced with the values given during expansion. If a parameter is not given any value during expansion, the default value is used. If there is no default value in the definition, not supplying a value causes an error.
Macro definitions cannot be nested - that is, a macro definition cannot contain another macro definition. But a macro definition can have
<?expand?>
instructions to expand another macro within this macro (line 17 in the example).The macro expansion and the parameter replacement work on just strings - there is no schema validation or XPath evaluation at this stage. A macro expansion just inserts the macro definition at the expansion site.
Macros can be defined in multiple files, and macros defined in the same package are visible to all templates in that package. This means that a template file could have just the definitions of macros, and another file in the same package could use those macros.
When reporting errors in a template using macros, the line numbers for the macro invocations are also included, so that the actual location of the error can be traced. For example, an error message might resemble service.xml:19:8 Invalid parameters for processing instruction set.
- meaning that there was a macro expansion on line 19 in service.xml
and an error occurred at line 8 in the file defining that macro.
XPath Context in Templates
When the evaluation of a template starts, the XPath context node and root node are both set to either the service instance data node (with a template-only service) or the node specified with the API call to apply the template (usually the service instance data node as well).
The root node is used as the starting point for evaluating absolute paths starting with /
and puts a limit on where you can navigate with ../
.
You can access data outside the current root node subtree by dereferencing a leafref type leaf or by changing the root node from within the template.
To change the root node within the template, use the set-root-node
XML processing instruction. The instruction takes an XPath expression as a parameter and this expression is evaluated in a special context, where the root node is the root of the datastore. This makes it possible to change to a node outside the current evaluation context.
For example: <?set-root-node {/}?>
changes the accessible tree to the whole data store. Note that, as all processing instructions, the effect of set-root-node
only applies until the closing parent tag.
The context node refers to the node that is used as the starting point for navigation with relative paths, such as ../device
or device
.
You can change the current context node using the set-context-node
or other context-related processing instructions. For example: <?set-context-node {..}?>
changes the context node to the parent of the current context node.
There is a special case where NSO automatically changes the evaluation context as it progresses through and applies the template, which makes it easier to work with lists. There are two conditions required to trigger this special case:
The value being set in the template is the key of a list.
The XPath expression used for this key evaluates to a node set, not a value.
To illustrate, consider the following example.
Suppose you are using the template to configure interfaces on a device. Target device YANG model defines the list of interfaces as:
You also use a service model that allows configuring multiple links:
The context-changing mechanism allows you to configure the device interface with the specified address using the template:
The /links/link[0]/intf-name
evaluates to a node and the evaluation context node is changed to the parent of this node, /links/link[0]
, because name
is a key leaf. Now you can refer to /links/link[0]/intf-addr
with a simple relative path {intf-addr}
.
The true power and usefulness of context changing becomes evident when used together with XPath expressions that produce node sets with multiple nodes. You can create a template that configures multiple interfaces with their corresponding addresses (note the use of link
instead of link[0]
):
The first expression returns a node set possibly including multiple leafs. NSO then configures multiple list items (interfaces), based on their name. The context change mechanism triggers as well, making {intf-addr}
refer to the corresponding leaf in the same link definition. Alternatively, you can achieve the same outcome with a loop (see Loop Statements).
However, in some situations, you may not desire to change the context. You can avoid it by making the XPath expression return a value instead of a node/node-set. The simplest way is to use the XPath string()
function, for example:
Namespaces and Multi-NED Support
When a device makes itself known to NSO, it presents a list of capabilities (see Capabilities, Modules, and Revision Management), which includes what YANG modules that particular device supports. Since each YANG module defines a unique XML namespace, this information can be used in a template.
Hence, a template may include configuration for many diverse devices. The templating system streamlines this by applying only those pieces of the template that have a namespace matching the one advertised by the device (see Supporting Different Device Types).
Additionally, the system performs validation of the template against the specified namespace when loading the template as part of the package load sequence, allowing you to detect a lot of the errors at load time instead of at run time.
In case the namespace matching is insufficient, such as when you want to check for a particular version of a NED, you can use special processing instructions if-ned-id
or if-ned-id-match
. See Processing Instructions Reference for details and Supporting Different Device Types for an example.
However, strict validation against the currently loaded schema may become a problem for developing generic, reusable templates that should run in different environments with different sets of NEDs and NED versions loaded. For example, an NSO instance having fewer NED versions than the template is designed for may result in some elements not being recognized, while having more NED versions may introduce ambiguities.
In order to allow templates to be reusable while at the same time keeping as many errors as possible detectable at load time, NSO has a concept of supported-ned-ids
. This is a set of NED IDs the package developer declares in the package-meta-data.xml
file, indicating all NEDs the XML templates contained in this package are designed to support. This gives NSO a hint on how to interpret the template.
Namely, if a package declares a list of supported-ned-ids, then the templates in this package are interpreted as if no other ned-ids are loaded in the system. If such a template is attempted to be applied to a device with ned-id outside the supported list, then a run-time error is generated because this ned-id was not considered when the template was loaded. This allows us to ignore ambiguities in the data model introduced by additional NEDs that were not considered during template development.
If a package declares a list of supported-ned-ids and the runtime system does not have one or more declared NEDs loaded, then the template engine uses the so-called relaxed loading mode, which means it ignores any unknown namespaces and <?if-ned-id?>
clauses containing exclusively unknown ned-ids, assuming that these parts of the template are not applicable in the current running system.
Because relaxed loading mode performs less strict validation and potentially prevents some errors from being detected, the package developer should always make sure to test in the system with all the supported ned-ids loaded, i.e. when the loading mode is strict
. The loading mode can be verified by looking at the value of template-loading-mode
leaf for the corresponding package under /packages/package
list.
If the package does not declare any supported-ned-ids
, then the templates are loaded in strict
mode, using the full set of currently loaded NED IDs. This may make the package less reusable between different systems, but is usually fine in environments where the package is intended to be used in runtime systems fully under the control of the package developer.
Passing Deep Structures from API
When applying the template via API, you typically pass parameters to a template through variables, as described in Templates and Code and Values in a Template. One limitation of this mechanism is that a variable can only hold one string value. Yet, sometimes there is a need to pass not just a single value, but a list, map, or even more complex data structures from API to the template.
One way to achieve this is to use smaller templates, such as invoking the template repeatedly, one by one for each list item (or perhaps pair-by-pair in the case of a map). However, there are certain disadvantages to this approach. One of them is the performance: every invocation of the template from the API requires a context switch between the user application process and the NSO core process, which can be costly. Another disadvantage is that the logic is split between Java or Python code and the template, which makes it harder to understand and implement.
An alternative approach described in this section involves modeling the required auxiliary data as operational data and populating it in the code, before applying the template. For a service, the service callback code in Java or Python first populates the auxiliary data and then passes control to the template, which handles the main service configuration logic. The auxiliary data is accessible in the template, by means of XPath, just like any other service input data.
There are different approaches to modeling the auxiliary data. It can reside in the service tree as it is private to the service instance; either integrated in the existing data tree or as a separate subtree under the service instance. It can also be located outside of the service instance, however, it is important to keep in mind that operational data cannot be shared by multiple services because there are no refcounters or backpointers stored on operational data.
After the service is deployed, the auxiliary leafs remain in the database which facilitates debugging because they can be seen via all northbound interfaces. If this is not the intention, they can be hidden with the help of tailf:hidden
statement. Because operational data is also a part of FASTMAP diff, these values will be deleted when the service is deleted and need to be recomputed when the service is re-deployed. This also means that in most cases there should be no need to write any additional code to clean up this data.
One example of a task that is hard to solve in the template by native XPath functions is converting a network prefix into a network mask or vice versa. Below is a snippet of a data model that is part of a service input data and contains a list of interfaces along with IP addresses to be configured on those interfaces. If the input IP address contains a prefix, but the target device accepts an IP address with a network mask instead, then you can use an auxiliary operational leaf to pass the mask (calculated from the prefix) to the template.
The code that calls the template needs to populate the mask. For example, using the Python Maagic API in a service:
The corresponding iface-template
might then be as simple as:
Service Callpoints and Templates
The archetypical use case for XML templates is service provisioning and NSO allows you to directly invoke a template for a service, without writing boilerplate code in Python or Java. You can take advantage of this feature by configuring the servicepoint
attribute on the root config-template
element. For example:
Adding the attribute registers this template for the given servicepoint, defined in the YANG service model. Without any additional attributes, the registration corresponds to the standard create service callback.
While the template (file) name is not referred to in this case, it must still be unique in an NSO node.
In a similar manner, you can register templates for each state of a nano service, using componenttype
and state
attributes. The section Nano Service Callbacks contains examples.
Services also have pre- and post-modification callbacks, further described in Service Callbacks, which you can also implement with templates. Simply put, pre- and post-modification templates are applied before and after applying the main service template.
These pre- and post-modification templates can only be used in classic (non-nano) services when the create callback is implemented as a template. That is, they cannot be used together with create callbacks implemented in Java or Python. If you want to mix the two approaches for the same service, consider using nano services.
To define a template as pre- or post-modification, appropriately configure the cbtype
attribute, along with servicepoint
. The cbtype
attribute supports these three values:
pre-modification
create
post-modification
NSO supports only a single registration for each servicepoint and callback type. Therefore, you cannot register multiple templates for the same servicepoint/cbtype
combination.
The $OPERATION
variable is set internally by NSO in pre- and post-modification templates to contain the service operation, i.e., create, update, or delete, that triggered the callback. The $OPERATION
variable can be used together with template conditional statements (see Conditional Statements) to apply different parts of the template depending on the triggering operation. Note that the service data is not available in the pre- or post-modification callbacks when $OPERATION = 'delete'
since the service has been deleted already in the transaction context where the template is applied.
Debugging Templates
You can request additional information when applying templates in order to understand what is going on. When applying or committing a template in the CLI, the debug
pipe command enables debug information:
The debug xpath
option outputs all XPath evaluations for the transaction, and is not limited to the XPath expressions inside templates.
The debug template
option outputs XPath expression results from the template, under which context expressions are evaluated, what operation is used, and how it affects the configuration, for all templates that are invoked. You can narrow it down to only show debugging information for a template of interest:
Additionally, the template and xpath debugging can be combined:
For XPath evaluation, you can also inspect the XPath trace log if it is enabled (e.g. with tail -f logs/xpath.trace
). XPath trace is enabled in the ncs.conf
configuration file and is enabled by default for the examples.
Another option to help you get the XPath selections right is to use the NSO CLI show
command with the xpath
display flag to find out the correct path to an instance node. This shows the name of the key elements and also the namespace changes.
When using more complex expressions, the ncs_cmd utility can be used to experiment with and debug expressions. ncs_cmd is used in a command shell. The command does not print the result as XPath selections but is still of great use when debugging XPath expressions. The following example selects FastEthernet interface names on the device c0
:
Example Debug Template Output
The following text walks through the output of the debug template
command for a dns-v3 example service, found in examples.ncs/implement-a-service/dns-v3
. To try it out for yourself, start the example with make demo
and configure a service instance:
The XML template used in the service is simple but non-trivial:
Applying the template produces a substantial amount of output. Let's interpret it piece by piece. The output starts with:
The templating engine found the foreach
in the dns-template.xml
file at line 4. In this case, it is the only foreach
block in the file but in general, there might be more. The {/target-device}
expression is evaluated using the /dns[name='instance1']
context, resulting in the complete /dns[name='instance1']/target-device
path. Note that the latter is based on the root node (not shown in the output), not the context node (which happens to be the same as the root node at the start of template evaluation).
NSO found two nodes in the leaf-list for this expression, which you can verify in the CLI:
Next comes:
The template starts with the first iteration of the loop with the c1
value. Since the node was an item in a leaf-list, the context refers to the actual value. If instead, it was a list, the context would refer to a single item in the list.
This line signifies the system “applied” line 6 in the template, selecting the c1
device for further configuration. The line also informs you the device (the item in the /devices/device list with this name) exists.
The template then evaluates the if
condition, resulting in processing of the lines 10 and 11 in the template:
The last line shows how a new value is added to the target leaf-list, that was not there (non-existing) before.
As the if
statement matched, the else
part does not apply and a new iteration of the loop starts, this time with the c2
value.
Now the same steps take place for the other, c2
, device:
Finally, the template processing completes as there are no more nodes in the loop, and NSO outputs the new dry-run configuration:
Processing Instructions Reference
NSO template engine supports a number of XML processing instructions to allow more dynamic templates:
Syntax | Description |
---|---|
Allows you to assign a new variable or manipulate the existing value of a variable v. If used to create a new variable, the scope of visibility of this variable is limited to the parent tag of the processing instruction or the current processing instruction block. Specifically, if a new variable is defined inside a loop, then it is discarded at the end of each iteration. | |
Processing instruction block that allows conditional execution based on the boolean result of the expression. For a detailed description, see Conditional Statements. | |
The expression must evaluate to a (possibly empty) XPath node-set. The template engine will then iterate over each node in the node set by changing the XPath current context node to this node and evaluating all children tags within this context. For the detailed description see Loop Statements. | |
This processing instruction allows you to iterate over the same set of template tags by changing a variable value. The variable visibility scope obeys the same rules as the Only the condition expression is mandatory, either or both of initial and next value assignment can be omitted, e.g.: For a detailed description see Loop Statements. | |
This instruction is analogous to | |
Allows to manipulate the root node of the XPath accessible tree. This expression is evaluated in an XPath context where the accessible tree is the entire datastore, which means that it is possible to select a root node outside the currently accessible tree. The current context node remains unchanged. The expression must evaluate to exactly one node in the data tree. | |
Allows you to manipulate the current context node used to evaluate XPath expressions in the template. The expression is evaluated within the current XPath context and must evaluate to exactly one node in the data tree. | |
Store both the current context node and the root node of the XPath accessible tree with | |
Used to switch to a context stored using | |
If there are multiple versions of the same NED expected to be loaded in the system, which define different versions of the same namespace, this processing instruction helps to resolve ambiguities in the schema between different versions of the NED. The part of the template following this processing instruction, up to matching
The The | |
The | |
Define a new macro with the specified name and optional parameters. Macro definitions must come at the top of the template, right after the | |
Insert and expand the named macro, using the specified values for parameters. For a detailed description, see Macros in Templates. |
The variable value in both set
and for
processing instructions are evaluated in the same way as the values within XML tags in a template (see Values in a Template). So, it can be a mix of literal values and XPath expressions surrounded by {...}
.
The variable value is always stored as a string, so any XPath expression will be converted to literal using the XPath string()
function. Namely, if the expression results in an integer or a boolean, then the resulting value would be a string representation of the integer or boolean. If the expression results in a node set, then the value of the variable is a concatenated string of values of nodes in this node set.
It is important to keep in mind that while in some cases XPath converts the literal to another type implicitly (for example, in an expression {$x < 3}
a value x='1' is converted to integer 1 implicitly), in other cases an explicit conversion is needed. For example, using the expression {$x > $y}
, if x='9' and y='11', the result of the expression is true due to alphabetic order as both variables are strings. In order to compare the values as numbers, an explicit conversion of at least one argument is required: {number($x) > $y}
.
XPath Functions
This section lists a few useful functions, available in XPath expressions. The list is not exhaustive; please refer to the XPath standard, YANG standard, and NSO-specific extensions in XPATH FUNCTIONS in Manual Pages for a full list.
Last updated