# Native-CLI translation units

# Modules structure

The following text block displays a structure of native-cli units that are placed under root 'cli-native-units' module with 2 device types - ios-xr-5 and junos-17. There are also init units under 'ios-xr' and 'junos' directories - they are still required to be implemented, however they are already part of classic translation units. The first identifier corresponds to directory name, the second identifier placed in brackets corresponds to module name. All modules are represented by 'pom.xml' files.

+ ios-xr (cli-units-ios-xr)
    + init (ios-xr-cli-init-unit)
+ junos (cli-units-junos)
    + init (junos-cli-unit-unit)
+ native-units (cli-native-units)
    + ios-xr-5 (ios-xr-5-native)
        + models (ios-xr-5-native-models)
        + unit (ios-xr-5-native-unit)
    + junos-17 (junos-17-native)
        + models (junos-17-native-models)
        + unit (junos-17-native-unit)
    + unit-parent (native-unit-parent)

Description of the modules:

  • cli-native-units: Root module that groups all native-CLI-only modules. Submodules are specified per device-type.
  • unit-parent: Parent unit common for all unit modules (for example 'ios-xr-5-native-unit' and 'junos-17-native-unit'): it specifies common imports. It doesn't need any modification when a new device-type is added.
  • ios-xr-5-native and junos-17-native: These modules just group unit and models submodules for specific device-types. Each supported device type should have its separated module.
  • ios-xr-5-native-models and junos-17-native-models: They contain all YANG schemas under "src/main/yang" directory - device-template YANG schemas and native-CLI YANG schemas. They are described in next sections in detail.
  • ios-xr-5-native-unit and junos-17-native-unit: Implementations of native-CLI translation units - these modules contain only single Java file under 'io.frinx.cli.cnative.iosxr5' or 'io.frinx.cli.cnative.junos17' package that is responsible for registration of YANGs and providing of device-specific information. More information can be found in the next section.
  • ios-xr-cli-init-unit and junos-cli-unit-unit: Reused initialization units that are required to be registered as native-cli translation units too. These units can be shared by both classic units which require implementations of handlers and native-CLI units. It is achieved by extending of 'AbstractUnitWithNativeSupport' abstract class.

# Implementation of units

# Device-specific units

All device-specific native-CLI units must extend 'GenericCliNativeUnit' abstract class. Description of the implemented methods:

  • getYangSchemas(): Returned set must contain all device-specific native-CLI schemas that are placed under models module except device-template YANG module that doesn't have to be placed to this set.
  • getRootInterfaces(): Returned list must contain all classes of root lists and containers (classes generated by MD-SAL generators from YANG schemas) - it simplifies transition between binding-aware and binding-independent worlds.
  • getSupportedVersions(): Set of supported device versions - it is used for identification of translation units.
  • getUnitName(): Name of the translation unit - it has only descriptive purposes.
  • getCliFlavour() (optional): By default, Cisco IOS CLI flavour is used. CLI flavour describes formatting of device running / candidate configuration that is used during parsing of configuration into the tree. Non-Cisco devices should override this method and provide custom CLI flavour in order to make native-CLI readers work (see next example with comments that describe CLI flavour parameters).

Example: Implementation of JUNOS 17 native-CLI unit:

public final class NativeCliUnit extends GenericCliNativeUnit {

    public NativeCliUnit(final TranslationUnitCollector registry, final DOMSchemaService domSchemaService,
                         final BindingNormalizedNodeSerializer mappingCodec) {
        super(registry, domSchemaService, mappingCodec);
    }

    @Override
    public Set<YangModuleInfo> getYangSchemas() {
        return ImmutableSet.of(org.opendaylight.yang.gen.v1.http.frinx.io.yang._native.junos17.firewall.rev200309
                        .$YangModuleInfoImpl.getInstance(),
                org.opendaylight.yang.gen.v1.http.frinx.io.yang._native.junos17.interfaces.rev200312
                        .$YangModuleInfoImpl.getInstance());
    }

    @Override
    protected List<Class<? extends DataObject>> getRootInterfaces() {
        return ImmutableList.of(Firewall.class, Interfaces.class);
    }

    @Override
    protected Set<Device> getSupportedVersions() {
        return Collections.singleton(JunosDevices.JUNOS_17);
    }

    @Override
    protected String getUnitName() {
        return "JUNOS 17.* native-cli unit";
    }

    @Override
    public CliFlavour getCliFlavour() {
        return new CliFlavour(
                Pattern.compile("^s(((h)?o)?w)? conf(((((((((i)?g)?u)?r)?a)?t)?i)?o)?n)?"), // show configuration pattern
                "    ", // indentation between sections
                "{", // beginning of the section (character)
                "}", // ending of the section (character)
                Pattern.compile("\\|"), OutputFunction.ALL, // output functions
                "##", // beginning of the line with comment
                Collections.emptyList(), // special non-parsable keywords that must be skipped from built tree
                ";", // ending of the command (character)
                Cli.NEWLINE, // newline separator
                ""); // username suffix used during authentication procedure
    }
}

# Init units

Rules for implementation of init units are same for native-CLI and classic units - see documentation: "Implementing CLI Translation Unit", subsection "Init Unit". The only difference is in the extended class - if an init unit must be registered as both native-CLI and classic translation unit (the most usual scenario), then init unit must extend 'AbstractUnitWithNativeSupport' and not just 'AbstractUnit' abstract class.

public class IosXrCliInitializerUnit extends AbstractUnitWithNativeSupport {
    /* ... */
}

# Device-template YANG model

These YANG schemas are used for describing of device-specific patterns that are required for successful communication with remote CLI. Device-template YANG schema doesn't contain any data schema nodes, it consists only from YANG extensions that are declared in the 'cli-native-extensions' model. Multiple native-CLI YANG models can import the same device-template model.

Sample device-template model for IOS XR 5.* devices:

module xr5-template {
    yang-version 1.1;
    namespace "http://frinx.io/yang/native/xr5/template";
    prefix xr5-template;

    import cli-native-extensions {
        revision-date "2020-03-09";
        prefix cne;
    }

    revision "2020-04-20" {
        description "Initial revision";
    }

    cne:show-command "show running-config";
    cne:config-pattern "#{command}";
    cne:delete-pattern "no #{command}";
}

Description of the supported extensions that can be used in a device-template:

  • show-command: Command used for displaying of the whole running / candidate configuration. It is used for initial population of the device configuration tree that is transformed into DOM format in native-CLI readers. The default string is "show running-config".
  • config-pattern: Template used for 'set' commands that apply a new configuration or update an existing configuration. It must contain '#' placeholder that is replaced by actual command that is going to be sent to remote CLI in native-CLI writers. Default string is "#" (without any prefix).
  • delete-pattern: Template used for 'delete' commands that remove some configuration from a device. It must contain '#' placeholder that is replaced by actual delete command that is going to be sent to remote CLI in native-CLI writers. Default string is "no #".

# Native-CLI YANG model

These YANG models are used for modelling of device configuration. Currently supported schema nodes include containers, lists, choice nodes, and leaves with different types. Groupings can also be freely used for organization of YANG structure. The following subsections explain general structure of the native-CLI YANG model and application of different schema nodes for modelling of device configuration with examples.

# Structure

The following YANG snippet shows structure that should all native-CLI YANG schemas follow (variable parts are marked by square brackets):

module [device-type]-[entity]-clinative {
    yang-version "1.1";
    namespace "http://frinx.io/yang/native/[device-type]/[entity]";
    prefix [prefix];

    import network-topology {
        prefix nt;
        revision-date "2013-10-21";
    }
    import frinx-uniconfig-topology {
        prefix ut;
        revision-date "2017-11-21";
    }
    import [template-model] {
        ...
    }

    revision [revision] {
        description [description];
    }

    ...
    grouping [root-grouping] {
        ...
    }

    uses [root-grouping];

    augment "/nt:network-topology/nt:topology/nt:node/ut:configuration" {
        uses [root-grouping];
    }
}

Description of variables:

  • [device-type]: Type of the device for which this YANG models some part of the configuration. Examples: junos17, xr5 (if it is necessary, more specific versions can be typed).
  • [entity]: Part of the configuration that is modelled by this YANG. Examples: interfaces, firewall, acl.
  • [prefix]: Prefix that is usually an abbreviation derived from the name of the model.
  • [template-model]: Name of the imported device-template module. Only a single device-template module can be imported, otherwise the whole module is marked as invalid and it is skipped. Afterwards, revision-date and prefix must be selected too.
  • [revision] with [description]: Date of the YANG modification with description, what was changed in the specific revision. Multiple revisions can be added incrementally as YANG schema is modified.
  • [root-grouping]: Identifier of the root grouping that contains single root container or list. Multiple root groupings are allowed when multiple root containers are lists are required. For each root grouping there must be a separate augmentation into 'configuration' container.

# Containers

Containers are used for representations of nodes in configuration which can have at least one child node but there is only one instance of configuration that is placed under this node. For example, let assume the following two lines in the XR 5.3.4 configuration of access-lists:

ipv6 access-list ACL6-01
 10 remark "This is just a test"

In this snippet, ipv6 is modelled by container schema node with the same identifier, because 'ipv6' command word is a root node and it can contain only single instance of list with identifier access-list:

container ipv6 {
    list access-list {
        ...
    }
}

It is also possible to wrap multiple containers in a chain. For example, the following command line:

ipv4 verify unicast source reachable-via any allow-self-ping

can be modelled by following containers:

container unicast {
    container source {
        container reachable-via {
            leaf any {
                ...
            }
        }
    }
}

# Lists

Similarly to containers, lists also represent command words that may have multiple children nodes. However, nodes represented by lists can have multiple instances in the configuration where individual instances are represented by one or multiple keys. Values of keys are represented by command nodes that follow list command word. For example, let consider following configuration of XR 5.3.4 interfaces:

interface MgmtEth0/0/CPU0/0
 ipv4 address 192.168.1.214 255.255.255.0
!
interface GigabitEthernet0/0/0/0
 shutdown
!
interface GigabitEthernet0/0/0/1
 shutdown
!
interface GigabitEthernet0/0/0/2
!

In this case, 'interface' can be modelled as list schema node with 'interface' identifier. It has a one key - interface name (possible values, based on the example, are 'MgmtEth0/0/CPU0/0', GigabitEthernet0/0/0/0, GigabitEthernet0/0/0/1, and GigabitEthernet0/0/0/2).

list interface {
    key "name";

    leaf name {
        type string;
    }
    ...
}

Name of the leaves that represent list keys are not important. Only an order of keys, in case of multiple keys, has a significance from the view of association between configuration and YANG model.

The second example presents a scenario in which a list with multiple keys must be used (IOS XR 5.3.4 access-lists):

ipv4 access-list ACL02
 10 deny esp 10.0.0.0/8 any icmp-off
 20 deny icmp 172.16.1.0/24 172.16.2.0/24 echo
 30 permit ipv4 any any

There are two keys - name of the access-list and sequence number of the access-list entry that must be unique in scope of the single access-list. Because of this, access-list can be modelled by following list schema node:

list access-list {
    key "name sequence-number";

    leaf name {
        type string;
    }
    leaf sequence-number {
        type uint32;
    }
    ...
}

# Choices

Identifiers of list, container, or leaf schema nodes are always derived from words identifying parts of the command lines. Choice schema nodes are modelled differently - they are only used for modelling of multiple non-overlapping sets of children commands. Both identifier of choice schema node and case nodes are not important (names of the case schema nodes are usually chosen based on logical option they represent). Choices are handy, if it is required to add YANG-based constraint on combinations of entered commands - wrong combinations of command would fail on device anyway.

For example, the JUNOS 17 allows configuration of different interface types:

ge-0/0/2 {
    hold-time up 100 down 80;
    disable;
}
ae10 {
    description "lacp interface 1";
    aggregated-ether-options {
        lacp {
            active;
            periodic fast;
        }
    }
}

In this example, 'hold-time' is a configuration that can be applied only on physical interfaces. On the other side, LACP can be configured only on the bundle interfaces. Because of this logical separation, it has a sense to differ between physical and bundle interfaces in YANG (common settings can be placed directly under 'interfaces' list):

list interfaces {
    key "name";

    leaf name {
        type string;
    }

    uses interface-common;

    choice interfaces {
        // aggregated-ethernet
        case ae {
            container aggregated-ether-options {
                ...
            }
            ...
        }
        // gigabit ethernet
        case ge {
            container hold-time {
                ...
            }
            ...
        }
    }
    ...
}

# Leaves

Leaves are used for representation of command parts that don't have next children subcommands. Command node can be represented by one or more words depending on the type of the leaf. The following types of leaves are currently supported:

1. Empty: Empty leaf can be used for commands without any value (there is only one command word that identifies leaf). For example, JUNOS 17 interface 'disable' command:

interfaces {
    ge-0/0/2 {
        disable;
    }
}

can be modelled as leaf with empty type:

leaf disable {
    type empty;
}

2. Types with primitive value: Supported primitive types include boolean, string, decimal, int8, int16, int32, int64, uint8, uint16, uint32, and uint64. All of these types can be used for commands that has a single string, boolean, or numeric value (types constrained by a range). The following commands can be modelled as one of these types (different JUNOS 17 damping settings):

interfaces ge-0/0/0 damping half-life 20
interfaces ge-0/0/0 damping suppress 100

YANG representation of leaves 'half-life' and 'suppress':

leaf half-life {
    type uint32;
}
leaf suppress {
    type uint32;
}

3. Enumeration - If there are multiple but finite set of possible strings assignable to the command, then the enumeration type should be used. Let consider the following variations of the 'mode' command (IOS XR 5.3.4 LACP configuration):

interface GigabitEthernet0/0/0/1 bundle mode active
interface GigabitEthernet0/0/0/1 bundle mode passive
interface GigabitEthernet0/0/0/1 bundle mode on

In this example, 'mode' is modelled as leaf with type enumeration with three possible values:

leaf mode {
    type enumeration {
        enum active;
        enum passive;
        enum on;
    }
}

4. Bits: This type of leaf can be used in scenarios in which there are multiple possible values assignable to the command (similarly to enumeration), but they are not mutually exclusive - different values can be combined in a chain of strings. Consider the following options how to configure Unicast Reverse Path Forwarding on IOS XR 5.3.4:

ipv6 verify unicast source reachable-via any allow-self-ping allow-default
ipv6 verify unicast source reachable-via any allow-default allow-self-ping
ipv6 verify unicast source reachable-via any allow-self-ping
ipv6 verify unicast source reachable-via any allow-default

The part of the command line starting by word 'any' can continue with random combination of options 'allow-self-ping' and 'allow-default' with random ordering too. Because of this reason, leaf with identifier 'any' has bits type:

leaf any {
    type bits {
        bit allow-default;
        bit allow-self-ping;
    }
}

5. Blob-data - It is a special type of leaf defined in 'cli-native-extensions' that can be used for the whole command section with a random structure. It is handy for the parts of the configuration that are too complicated to be represented by different YANG structures. Internally, 'blob-data' is a type definition derived from string type. For example, JUNOS 17 firewall rules fulfils high complexity:

term rule-1 {
    from {
        address {
            be::01/128;
            be::02/128;
        }
        port snmp;
    }
    then {
        log;
        discard;
    }
}

Commands 'from' and 'then' can be represented by leaves with 'blob-type':

import cli-native-extensions {
    prefix cne;
    revision-date "2020-03-09";
}

leaf from {
    type cne:blob-data;
}
leaf then {
    type cne:blob-data;
}