CLI NED Development
Create CLI NEDs.
The CLI NED is a model-driven way to CLI script towards all Cisco-like devices. Some Java code is necessary for handling the corner cases a human-to-machine interface presents.
See the examples.ncs/device-manager/cli-ned for an example of a Java implementation serving any YANG models, including those that come with the example.
The NSO CLI NED southbound of NSO shares a Cisco-style CLI engine with the northbound NSO CLI interface, and the CLI engine can thus run in both directions, producing CLI southbound and interpreting CLI data coming from southbound while presenting a CLI interface northbound. It is helpful to keep this in mind when learning and working with CLI NEDs.
A sequence of Cisco CLI commands can be turned into the equivalent manipulation of the internal XML tree that represents the configuration inside NSO.
A YANG model, annotated appropriately, will produce a Cisco CLI. The user can enter Cisco commands, and NSO will parse the Cisco CLI commands using the annotated YANG model and change the internal XML tree accordingly. Thus, this is the CLI parser and interpreter. Model-driven.
The reverse operation is also possible. Given two different XML trees, each representing a configuration state, in the netsim/ConfD case and NSO's northbound CLI interface, it represents the configuration of a single device, i.e., the device using ConfD as a management framework. In contrast, the NSO case represents the entire network configuration and can generate the list of Cisco commands going from one XML tree to another.
NSO uses this technology to generate CLI commands southbound when we manage Cisco-like devices.
It will become clear later in the examples how the CLI engine runs in forward and reverse mode. The key point though, is that the Cisco CLI NED Java programmer doesn't have to understand and parse the structure of the CLI; this is entirely done by the NSO CLI engine.
To implement a CLI NED, the following components are required:
A YANG data model that describes the CLI. An important development tool here is netsim (ConfD), the Tail-f on-device management toolkit. For NSO to manage a CLI device, it needs a YANG file with exactly the right annotations to produce precisely the managed device's CLI. A few examples exist in the NSO NED evaluation collection with annotated YANG models that render different Cisco CLI variants.
See, for example,
$NCS_DIR/packages/neds/dell-ftosand$NCS_DIR/packages/neds/cisco-nx. Look fortailf:cli-*extensions in the NEDsrc/yangdirectory YANG models.Thus, to create annotated YANG files for a device with a Cisco-like CLI, the work procedure is to run netsim (ConfD) and write a YANG file that renders the correct CLI.
Furthermore, this YANG model must declare an identity with
ned:cli-ned-idas a base.It is important to note that a NED only needs to cover certain aspects of the device. To have NSO manage a device with a Cisco-like CLI you do not have to model the entire device, only the commands intended to be used need to be covered. When the
show()callback issues itsshow running-config [toptag]command and the device replies with data that is fed to NSO, NSO will ignore all command dump output that the loaded YANG models do not cover.Thus, whichever Cisco-like device we wish to manage, we must first have YANG models from NSO that cover all aspects of the device we want to use. Once we have a YANG model, we load it into NSO and modify the example CLI NED class to return the NedCapability list of the device.
The NED code gets to see all data from and to the device. If it's impossible or too hard to get the YANG model exactly right for all commands, a last resort is to let the NED code modify the data inline.
The next thing required is a Java class that implements the NED. This is typically not a lot of code, and the existing example NED Java classes are easily extended and modified to fit other needs. The most important point of the Java NED class code is that the code can be oblivious to the CLI commands sent and received.
Java CLI NED code must implement the CliNed interface.
NedConnectionBase.java. See$NCS_DIR/java/jar/ncs-src.jar. Use jar xf ncs-src.jar to extract the JAR file. Look forsrc/com/tailf/ned/NedConnectionBase.java.NedCliBase.java. See$NCS_DIR/java/jar/ncs-src.jar. Use jar xf ncs-src.jar to extract the JAR file. Look forsrc/com/tailf/ned/NedCliBase.java.
Thus, the Java NED class has the following responsibilities.
It must implement the identification callbacks, i.e
modules(),type(), andidentity()It must implement the connection-related callback methods
newConnection(),isConnection()andreconnect()NSO will invoke the
newConnection()when it requires a connection to a managed device. ThenewConnection()method is responsible for connecting to the device, figuring out exactly what type of device it is, and returning an array ofNedCapabilityobjects.\This is very much in line with how a NETCONF connect works and how the NETCONF client and server exchange hello messages.
Finally, the NED code must implement a series of data methods. For example, the method
void prepare(NedWorker w, String data)get aStringobject which is the set of Cisco CLI commands it shall send to the device.In the other direction, when NSO wants to collect data from the device, it will invoke
void show(NedWorker w, String toptag)for each tag found at the top of the data model(s) loaded for that device. For example, if the NED gets invoked withshow(w, "interface")it's responsibility is to invoke the relevant show configuration command for "interface", i.e.show running-config interfaceover the connection to the device, and then dumbly reply with all the data the device replies with. NSO will parse the output data and feed it into its internal XML trees.NSO can order the
showPartial()to collect part of the data if the NED announces the capabilityhttp://tail-f.com/ns/ncs-ned/show-partial?path-format=FORMATin which FORMAT is of the following:key-path: support regular instance keypath format.
top-tag: support top tags under the
/devices/device/configtree.cmd-path-full: support Cisco's CLI edit path with instances.
path-modes-only: support Cisco CLI mode path.
cmd-path-modes-only-existing: same as
path-mode-onlybut NSO only supplies the path mode of existing nodes.
Writing a Data Model for a CLI NED
The idea is to write a YANG data model and feed that into the NSO CLI engine such that the resulting CLI mimics that of the device to manage. This is fairly straightforward once you have understood how the different constructs in YANG are mapped into CLI commands. The data model usually needs to be annotated with a specific Tail-f CLI extension to tailor exactly how the CLI is rendered.
This section will describe how the general principles work and give a number of cookbook-style examples of how certain CLI constructs are modeled.
The CLI NED is primarily designed to be used with devices that has a CLI that is similar to the CLIs on a typical Cisco box (i.e. IOS, XR, NX-OS, etc). However, if the CLI follows the same principles but with a slightly different syntax, it may still be possible to use a CLI NED if some of the differences are handled by the Java part of the CLI NED. This section will describe how this can be done.
Let's start with the basic data model for CLI mapping. YANG consists of three major elements: containers, lists, and leaves. For example:
The basic rendering of the constructs is as follows. Containers are rendered as command prefixes which can be stacked at any depth. Leaves are rendered as commands that take one parameter. Lists are rendered as submodes, where the key of the list is rendered as a submode parameter. The example above would result in the command:
For entering the interface ethernet submode. The interface is a container and is rendered as a prefix, ethernet is a list and is rendered as a submode. Two additional commands would be available in the submode:
A typical configuration with two interfaces could look like this:
Note that it makes sense to add help texts to the data model since these texts will be visible in the NSO and help the user see the mapping between the J-style CLI in the NSO and the CLI on the target device. The data model above may look like the following with proper help texts.
I will generally not include the help texts in the examples below to save some space but they should be present in a production data model.
Tweaking the Basic Rendering Scheme
The basic rendering suffice in many cases but is also not enough in many situations. What follows is a list of ways to annotate the data model in order to make the CLI engine mimic a device.
Suppressing Submodes
Sometimes you want a number of instances (a list) but do not want a submode. For example:
The above would result in the following commands:
A typical show-config output may look like:
Adding a Submode
Sometimes you want a submode to be created without having a list instance, for example, a submode called aaa where all AAA configuration is located.
This is done by using the tailf:cli-add-mode extension. For example:
This would result in the command aaa for entering the container. However, sometimes the CLI requires that a certain set of elements are also set when entering the submode, but without being a list. For example, the police rules inside a policy map in the Cisco 7200.
Here, the leaves with the annotation tailf:cli-hide-in-submode is not present as commands once the submode has been entered, but are instead only available as options the police command when entering the police submode.
Commands with Multiple Parameters
Often a command is defined as taking multiple parameters in a typical Cisco CLI. This is achieved in the data model by using the annotations tailf:cli-sequence-commands, tailf:cli-compact-syntax, tailf:cli-drop-node-name, and possibly tailf:cli-reset-siblings.
For example:
This results in the command:
The tailf:cli-sequence-commands annotation tells the CLI engine to process the leaves in sequence. The tailf:cli-reset-siblings tells the CLI to reset all leaves in the container if one is set. This is necessary in order to ensure that no lingering config remains from a previous invocation of the command where more parameters were configured. The tailf:cli-drop-node-name tells the CLI that the leaf name shouldn't be specified. The tailf:cli-compact-syntax annotation tells the CLI that the leaves should be formatted on one line, i.e. as:
As opposed to without the annotation:
When constructs are used to control if the numerical value should be the milli or the secs leaf.
This command could also be written using a choice construct as:
Sometimes the tailf:cli-incomplete-command is used to ensure that all parameters are configured. The cli-incomplete-command only applies to the C- and I-style CLI. To ensure that prior leaves in a container are also configured when the configuration is written using J-style or Netconf proper 'must' declarations should be used.
Another example is this, where tailf:cli-optional-in-sequence is used:
The tailf:cli-optional-in-sequence means that the parameters should be processed in sequence but a parameter can be skipped. However, if a parameter is specified then only parameters later in the container can follow it.
It is also possible to have some parameters in sequence initially in the container, and then the rest in any order. This is indicated by the tailf:cli-break-sequence command. For example:
Where it is possible to write:
As well as:
Leaf Values Not Really Part of the Key
Sometimes a command for entering a submode has parameters that are not really key values, i.e. not part of the instance identifier, but still need to be given when entering the submode. For example
In this case, the tcpudp is a non-key leaf that needs to be specified as a parameter when entering the service-group submode. Once in the submode the commands backup-server-event-log and extended-stats are present. Leaves with the tailf:cli-hide-in-submode attribute are given after the last key, in the sequence they appear in the list.
It is also possible to allow leaf values to be entered in between key elements. For example:
Here we have a list that is not mapped to a submode. It has two keys, read and remote, and an optional oid that can be specified before the remote key. Finally, after the last key, an optional mask parameter can be specified. The use of the tailf:cli-expose-key-name means that the key names should be part of the command, which they are not by default. The above construct results in the commands:
The tailf:cli-reset-container attribute means that all leaves in the container will be reset if any leaf is given.
Change Controlling Annotations
Some devices require that a setting be removed before it can be changed, for example, the service-group list above. This is indicated with the tailf:cli-remove-before-change annotation. It can be used both on lists and on leaves. A leaf example:
This means that the diff sent to the device will contain first a no source-ip command, followed by a new source-ip command to set the new value.
The data model also use the tailf:cli-no-value-on-delete annotation which means that the leaf value should not be present in the no command. With the annotation, a diff to modify the source IP from 1.1.1.1 to 2.2.2.2 would look like:
And, without the annotation as:
Ordered-by User Lists
By default, a diff for an ordered-by-user list contains information about where a new item should be inserted. This is typically not supported by the device. Instead, the commands (diff) to send the device needs to remove all items following the new item, and then reinsert the items in the proper order. This behavior is controlled using the tailf:cli-long-obu-diff annotation. For example
Suppose we have the access list:
And we want to change this to:
We would generate the diff with the tailf:cli-long-obu-diff:
Without the annotation, the diff would be:
Default Values
Often in a config when a leaf is set to its default value it is not displayed by the show running-config command, but we still need to set it explicitly. Suppose we have the leaf state. By default, the value is active.
If the device state is block and we want to set it to active, i.e. the default value. The default behavior is to send to the device:
This will not work. The correct command sequence should be:
The way to achieve this is to do the following:
This way a value for 'state' will always be generated. This may seem unintuitive but the reason this works comes from how the diff is calculated. When generating the diff the target configuration and the desired configuration is compared (per line). The target config will be:
And the desired config will be:
This will be interpreted as a leaf value change and the resulting diff will be to set the new value, i.e. active.
However, without the cli-show-with-default option, the desired config will be an empty line, i.e. no value set. When we compare the two lines we get:
(current config)
(desired config)
This will result in the command to remove the configured leaf, i.e.
Which does not work.
Understanding How the Diffs are Generated
What you see in the C-style CLI when you do 'show configuration' is the commands needed to go from the running config to the configuration you have in your current session. It usually corresponds to the command you have just issued in your CLI session, but not always.
The output is actually generated by comparing the two configurations, i.e. the running config and your current uncommitted configuration. It is done by running 'show running-config' on both the running config and your uncommitted config, and then comparing the output line by line. Each line is complemented by some meta information which makes it possible to generate a better diff.
For example, if you modify a leaf value, say set the MTU to 1400 and the previous value was 1500. The two configs will then be
When we compare these configs, the first lines are the same -> no action but we remember that we have entered the FastEthernet0/0/1 submode. The second line differs in value (the meta-information associated with the lines has the path and the value). When we analyze the two lines we determine that a value_set has occurred. The default action when the value has been changed is to output the command for setting the new value, i.e. MTU 1500. However, we also need to reposition to the current submode. If this is the first line we are outputting in the submode we need to issue the command before issuing the MTU 1500 command.
Similarly, suppose a value has been removed, i.e. mtu used to be set but it is no longer present
As before, the first lines are equivalent, but the second line has a ! in the new config, and MTU 1400 in the running config. This is analyzed as being a delete and the commands are generated:
There are tweaks to this behavior. For example, some machines do not like the no command to include the old value but want instead the command:
We can instruct the CLI diff engine to behave in this way by using the YANG annotation tailf:cli-no-value-on-delete;:
It is also possible to tell the CLI engine to not include the element name in the delete operation. For example the command:
But the command to delete the password is:
The data model for this would be:
Modifying the Java Part of the CLI NED
It is often necessary to do some minor modifications to the Java part of a CLI NED. There are mainly four functions that needs to be modified: connect, show, applyConfig, and enter/exit config mode.
Connecting to a Device
The CLI NED code should do a few things when the connect callback is invoked.
Set up a connection to the device (usually SSH).
If necessary send a secondary password to enter exec mode. Typically a Cisco IOS-like CLI requires the user to give the
enablecommand followed by a password.Verify that it is the right kind of device and respond to NSO with a list of capabilities. This is usually done by running the
show versioncommand, or equivalent, and parsing the output.Configure the CLI session on the device to not use pagination. This is normally done by setting the screen length to 0 (or infinity or disable). Optionally it may also fiddle with the idle time.
Some modifications may be needed in this section if the commands for the above differ from the Cisco IOS style.
Displaying the Configuration of a Device
The NSO will invoke the show() callback multiple times, one time for each top-level tag in the data model. Some devices have support for displaying just parts of the configuration, others do not.
For a device that cannot display only parts of a config the recommended strategy is to wait for a show() invocation with a well known top tag and send the entire config at that point. If, if you know that the data model has a top tag called interface then you can use code like:
From the point of NSO, it is perfectly ok to send the entire config as a response to one of the requested toptags and to send an empty response otherwise.
Often some filtering is required of the output from the device. For example, perhaps part of the configuration should not be sent to NSO, or some keywords replaced with others. Here are some examples:
Stripping Sections, Headers, and Footers
Some devices start the output from show running-config with a short header, and some add a footer. Common headers are Current configuration: and a footer may be end or return. In the example below we strip out a header and remove a footer.
Also, you may choose to only model part of a device configuration in which case you can strip out the parts that you have not modelled. For example, stripping out the SNMP configuration:
Removing Keywords
Sometimes a device generates non-parsable commands in the output from show running-config. For example, some A10 devices add a keyword cpu-process at the end of the ip route command, i.e.:
However, it does not accept this keyword when a route is configured. The solution is to simply strip the keyword before sending the config to NSO and to not include the keyword in the data model for the device. The code to do this may look like this:
Replacing Keywords
Sometimes a device has some other names for delete than the standard no command found in a typical Cisco CLI. NSO will only generate no commands when, for example, an element does not exist (i.e. no shutdown for an interface), but the device may need undo instead. This can be dealt with as a simple transformation of the configuration before sending it to NSO. For example:
Another example is the following situation. A device has a configuration for port trunk permit vlan 1-3 and may at the same time have disallowed some VLANs using the command no port trunk permit vlan 4-6. Since we cannot use a no container in the config, we instead add a disallow container, and then rely on the Java code to do some processing, e.g.:
And, in the Java show() code:
A similar transformation needs to take place when the NSO sends a configuration change to the device. A more detailed discussion about apply config modifications follows later but the corresponding code would in this case be:
Different Quoting Practices
If the way a device quotes strings differ from the way it can be modeled in NSO, it can be handled in the Java code. For example, one device does not quote encrypted password strings which may contain odd characters like the command character !. Java code to deal with this may look like:
And similarly de-quoting when applying a configuration.
Applying a Config
NSO will send the configuration to the device in three different callbacks: prepare(), abort(), and revert(). The Java code should issue these commands to the device but some processing of the commands may be necessary. Also, the ongoing CLI session needs to enter configure mode, issue the commands, and then exit configure mode. Some processing may be needed if the device has different keywords, or different quoting, as described under the "Displaying the configuration of a device" section above.
For example, if a device uses undo in place of no then the code may look like this, where data is the string of commands received from NSO:
This relies on the fact that NSO will not have any indentation in the commands sent to the device (as opposed to the indentation usually present in the output from show running-config).
Tail-f CLI NED Annotations
The typical Cisco CLI has two major modes, operational mode and configure mode. In addition, the configure mode has submodes. For example, interfaces are configured in a submode that is entered by giving the command interface <InterfaceType> <Number>. Exiting a submode, i.e. giving the exit command, leaves you in the parent mode. Submodes can also be embedded in other submodes.
In a typical Cisco CLI, you do not necessary have to exit a submode to execute a command in a parent mode. In fact, the output of the command show running-config hardly contains any exit commands. Instead, there is an exclamation mark, !, to indicate that a submode is done, which is only a comment. The config is formatted to rely on the fact that if a command isn't found in the current submode, the CLI engine searches for the command in its parent mode.
Another interesting mapping problem is how to interpret the no command when multiple leaves are given on a command line. Consider the model:
It corresponds to the command syntax foo [a <word> [b <word> [c <word>]]], i.e. the following commands are valid:
Now what does it mean to write no foo a <word> b <word> c <word>? . It could mean that only the c leaf should be removed, or it could mean that all leaves should be removed, and it may also mean that the foo container should be removed.
There is no clear principle here and no one right solution. The annotations are therefore necessary to help the diff engine figure out what to actually send to the device.
Annotations
The full set of annotations can be found in the tailf_yang_cli_extensions Manual Page. All annotation YANG extensions are not applicable in an NSO context, but most are. The most commonly used annotations are (in alphabetical order):
Last updated
Was this helpful?

