Topic: tech juniper jaut prev next

tech juniper jaut > Module 07: Junos Op Scripts

Module 07: Junos Op Scripts

Can be executed manually from the CLI, upon login, or as part of another script or a NETCONF session. Here is a ‘hello world’ script. The ‘ns’ declarations are boilerplate and will be excluded from future examples.

version 1.0;

ns junos = "http://xml.juniper.net/junos/*/junos";
ns xnm = "http://xml.juniper.net/xnm/1.1/xnm";
ns jcs = "http://xml.juniper.net/junos/commit-scripts/1.0";

import "../imports/junos.xsl";

match / {
    <op-script-results> {
        <output> "Hello, world!";
    }
}

Making an Op Script Available

Copy the script to /var/db/scripts/op. Configure the op script at the [edit system scripts] hierarchy.

set system scripts op file helloworld.slax
show system scripts
op {
    file helloworld.slax
}

Run the script with the ‘op’ command, omitting the extension for .slax, .xsl, and .py files.

op helloworld

Standard Arguments

The following arguments are available to every script:

The SLAX ‘param’ statement is used to process arguments. If parameters are not specified in the CLI, their value will be passed as empty.

version 1.0;
import "../imports/junos.xsl";

param $message;

match / {
    <op-script-results> {
        <output> $message;
    }
}

Values can be passed as follows;

op helloworld message "Hello, world!"

Declaring Arguments in an Op Script

Provide context-sensitive help on how to use the script.

var $arguments = {
    <argument> {
        <name> "message";
        <description> "The message that should be printed.";
    }
}

jcs:invoke

The jcs:invoke(rpc) function invokes an RPC on the device. It takes and returns XML.

To find the corrent RPC from the CLI, use;

show interfaces | display xml rpc

Alternatively, consult the documentation.

var $intoutput = jcs:invoke('get-interface-information');

for-each ($intoutput/physical-interface) {
    expr jcs:outptu(name);
}

XML input can also be passed to jcs:invoke().

var $interface-request = <get-interface-information. {
    <interface-name> "ge-1/0/0";
    <extensive>
}
var $intoutput = jcs:invoke($interface-request);

View the op script log with;

show log op.log

XMLRPC requests can return errors. The error will be enclosed in an xnm:error tag. An error is detected by the presence of this tag.

var $ping-request = <ping> {
    <host> "10.0.20.1";
    <routing-instance> "soinos";
}
var $ping-output = jcs:invoke($ping-request);
if ($ping-output//xnm:error) {
    <output> "Ping command returned an error: " _ $ping-output//xnm:error/message;
}

Other SLAX Output Options

These two options result in the same output.

<output> {
    expr "The IP address is ";
    expr $ip-address;
}

<output> "The IP address is " _ $ip-address;

Built-in CLI Formatting

The CLI already translates XMLRPC responses into CLI output when the user runs a command. If an XML tag recognised by the CLI is output from an op script, the output is automatically translated into CLI format. Alternatively, output can be formatted manually using the tag.

param $autonomous-system;
var $arguments = {
    <argument> {
        <name> "autonomous-system";
        <description> "The AS to display results for.";
    }
}
match / {
    <op-script-results> {
        var $bgp-info = jcs:invoke('get-bgp-summary-information');
        <output> "header";
        <bgp-information> {
            if (jcs:empty($autonomous-system)) {
                for-each ($bgp-info/bgp-peer) {
                    <bgp-peer junos:style="terse"> {
                        copy-of ./*;
                    }
                }
             } else {
                for-each ($bgp-info/bgp-peer[peer-as=$autonomous-system]) {
                    <bgp-peer junos:style="terse"> {
                        copy-of ./*;
                    }
                }
             }
        }
    }
}

Script Example

param $interface
var $arguments = {
    <argument> {
        <name> "interface";
        <description> "The interface to disable & re-enable.";
    }
}
match / {
    <op-script-results> {
        if (jcs:empty($interface$)) {
            <xnm:error> {
                <message> "Required parameter 'interface' missing.";
            }
        } else {
            var $con = jcs:open();
            if (not($con)) {
                <xnm:error> {
                    <message> "Unable to connect to local mgd.";
                }
            }
            else {
                var $disable-xml = {
                    <configuration> {
                        <interfaces> {
                            <interface> {
                                <name> $interface;
                                <disable>;
                            }
                        }
                    }
                }
                expr jcs:output("Disabling interface...");
                var $disable-results := {
                    call jcs:load-configurtion($connection=$con, $configuration=$disable-xml);
                }
                <output> "Results of commit to disable the interface:";
                copy-of $disable-results;
                if ($disable-results//xnm:error) {
                    <xnm:error> {
                        <output> "Script aborted due to errors while disabling the interface.";
                    }
                } else {
                    expr jcs:output("Interface ", $interface, " disabled.");
                    expr jcs:sleep(5,0);
                }
                var $enable-xml = {
                    <configuration> {
                        <interfaces> {
                            <interface> {
                                <name> $interface;
                                <disable delete="delete">;
                            }
                        }
                    }
                }
                expr jcs:output("Enabling interface...");
                var $enable-results := {
                    call jcs:load-configurtion($connection=$con, $configuration=$enable-xml);
                }
                <output> "Results of commit to enable the interface:";
                copy-of $enable-results;
                if ($enable-results//xnm:error) {
                    <xnm:error> {
                        <message> "Error enabling the interface.";
                    }
                } else {
                    expr jcs:output("Interface ", $interface, " enabled.");
                }
            }
            expr jcs:close($con);
        }
    }
}

On-box Python Scripting

Junos does not execute unsigned Python scripts by default. Set ‘system scripts language python’ to enable execution of unsigned Python scripts.

show system scripts
op {
    file hello-python.py;
}
language python;

Save scripts to the appropriate location.

Set ‘system scripts load-scripts-from-flash’ to enable flash storage.

        Hard drive storage      Flash storage
OP      /var/db/scripts/op      /config/scripts/op
Commit  /var/db/scripts/commit  /config/scripts/commit
Event   /var/db/scripts/event   /config/scripts/event
SNMP    /var/db/scripts/snmp    /config/scripts/snmp

For unsigned scripts to run: * The script owner is either root or a member of the super-user class. * Only the script owner has write permission for the file. * The script executer has read permission for the file.

Example Python Script

The only notable difference from an off-box script is that Device() requires no parameters to represent the local device.

from jnpr.junos import Device
if __name__ == "__main__":
    with Device() as dev:
        print(dev.facts)

Using Arguments with Python Scripts

Place arguments in a dictionary. The dictionary must be called ‘arguments’ and contain key-value pairs. argparse only parses the arguments when the script is running.

import argparse
arguments = {
    "interface": "Interface to display"
}

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    for key in arguments:
        parser.add_argument('-' + key, required=True, help=arguments[key])
    args = parser.parse_args()
    print(args)

Executing RPC’s

with Device() as dev:
    res = dev.rpc.get_interface_information(interface_name=args.interface, terse=True, normalize=True)

    print args.interface + " status: " + res.findtext("logical-interface/oper-status")

    XPATH = "//address-family[address-family-name=$family]/interface-address/ifa-local"

    # Copy the 'family' variable into the XPath context.
    for elem in res.xpath(XPATH, family=args.family):
        print args.family + " address  " + elem.text

Modifying the Configuration

from jnpr.junos import Device
from jnpr.junos.utils.config import Config
from jnpr.junos.exception import U
import argparse
from time import sleep
arguements = {
    "interface": "",
    "delay": ""
}

def change_config(dev_cfg, set_cmds, op_descr):

    print op_descr + " : Locking the configuration"
    try:
        dev_cfg.lock()
    except LockError:
        print "Error: unable to lock"
        return False

    print op_descr + " : Loading configuration changes"
    try:
        dev_cfg.load(set_cmds, format="set")
    except ConfigLoadError as err:
        print "Unable to load coniguration changes: \n" + err
        print Unlocking the configuration
        try:
            dev_cfg.unlock()
        except UnlockError:
            print "Error: Unable to unlock configuration"
        return False

    print op_descr + " : Committing the configuration"
    try:
        dev_cfg.commit()
    except CommitError:
        print "Error: Unable to commit the configuration"
        print "Unlocking the configuration"
        try:
            dev_cfg.unlock()
        except UnlockError:
            print "Error: Unable to unlock configuration"
        return False

    print op_descr + " : Unlocking the configuration"
    try:
        dev_cfg.unlock()
    except UnlockError:
        print "Error: Unable to unlock the configuration"
        return False

    return True

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    for key in arguments:
        parser.add_argument('-' + key, required=True, help=arguments[key])
    args = parser.parse_args()

    with Device() as dev:
        with Config(dev) as conf;
            if change_config(conf, "set interfaces " + args.interface + " disable", "Disabling interface"):
                print "Waiting " + args.delay + " seconds"
                sleep(float(args.delay))
                if change_config(conf, "delete interfaces " + args.interface + " disable", "Enabling interface"):
                    print "Interface bounced"
                else:
                    print "Error re-enabling"
            else:
                print "Error disabling interface"

Install the script by configuring:

set system scripts op file bounce.py

Run the script with;

op bounce.py interface ge-0/0/0 delay 5

Additional Script Configuration

A description, command and remote source can be configured for a script;

set system scripts op file hello.py source http://..../hello.py
set system scripts op file hello.py command pyhello
set system scripts op file hello.py description "My happy script"

Scripts can be refreshed from their remote source. The command is effective immediately and does not enter the configuration, nor require a commit;

set system scripts op file hello.py refresh

Global trace options can be configured;

set system scripts op traceoptions file opscript
set system scripts op traceoptions flag all

Script arguments can also be configured in the configuration, though configuring them in the script itself is probably preferred.

set system scripts op file hello.py arguments interface description "..."

Allow additional commands to be run by the script, even if the user running the script doesn’t have those permissions. Note that this only works for local scripts and not remote scripts.

set system scripts op file hello.py allow-commands restart

Scripts in other locations can be run without having been configured;

op url /var/home/lab/hello.py

SLAX scripts can be executed in this way by default. To execute Python scripts;

set system scripts op allow-url-for-python

To increase security, an SHA256 hash can be specified;

op url http://... key ...

Or, remote op scripts can be disabled entirely;

set system scripts op no-allow-url

Debugging Tools

In SLAX, use jcs:progress() to print a message only if the detail flag is set. Use jcs:trace() to log a message only if traceoptions is enabled.

Similarly, in Python, use jcs.progress() and jcs.trace().