Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
JUNOS Automation Cookbook

You're reading from   JUNOS Automation Cookbook Automate network devices on Juniper's operating system

Arrow left icon
Product type Paperback
Published in Sep 2017
Publisher Packt
ISBN-13 9781788290999
Length 382 pages
Edition 1st Edition
Tools
Concepts
Arrow right icon
Author (1):
Arrow left icon
Adam Chappell Adam Chappell
Author Profile Icon Adam Chappell
Adam Chappell
Arrow right icon
View More author details
Toc

Table of Contents (10) Chapters Close

Preface 1. Configuring JUNOS through NETCONF 2. Working with the Junos REST API FREE CHAPTER 3. Using SLAX to Write Op Scripts 4. Event Programming 5. Automating JUNOS with PyEZ 6. Advanced Visualization Applications 7. Monitoring and Maintaining JUNOS 8. Security Applications 9. Extending JUNOS with Ansible

Processing NETCONF with Node.js

Node.js is a popular JavaScript-based language used originally in the server-side web environment, but now common in many application spaces. Its key benefit is its modern JavaScript-dialect allowing object-oriented and prototypal object inheritance models, fused together with Google's efficient V8 JavaScript engine, and an asynchronous, event-based programming framework from the get-go. The asynchronous nature of Node.js makes it ideal for advanced automation and control applications where one needs to communicate with multiple elements at once.

In this recipe, we explore the use of a simple Node.js application acting as a NETCONF client in a similar manner to the previous Python and Expect/TCL applications.

Getting ready

In order to complete this recipe, you should have already completed the JUNOS NETCONF over SSH setup recipe and have a working JUNOS OS NETCONF host. You also need a Node.js installation on the operating system of your choice. For our testing, we used a variety of versions, from v0.10.35 through v6.10.0.

How to do it...

The steps for the recipe are as follows:

  1. Firstly, install a viable XML parsing library. Out of the box, Node.js ships with no XML parsing capability within its standard modules, so make use of the popular xml2js library, written by Marek Kubica, and install it using the npm package management tool:
    unix$ npm install xml2js
  1. Import the required Node.js modules to operate. In this case, we make use of the child_process module in order to control a child process and the XML parsing module:
    #!/usr/bin/env node

const util = require("util");
const child_process = require('child_process');
const xml2js = require('xml2js');
  1. Define some program constants that we can refer to consistently later, including the XML phrase for the command RPC and the invaluable NETCONF delimiter that denotes the space between XML messages:
    const DELIMITER="]]>]]>";

const xmlRpcCommand = function(command) {
return [
"<rpc>\n",
"<command format=\"text\">\n",
command,
"</command>",
"</rpc>\n",
DELIMITER,
"\n"
];
};
  1. Define a convenience utility subroutine for accessing the nested JavaScript object dictionaries, which will be the result of parsing the XML.
  var walk = function(obj, path) {
var result = obj;
path.forEach(function (cur, ind, array) {
if (result) result=result[cur];
});
return result;
}
  1. Parse the command-line arguments to determine a target hostname and a command:
  if (process.argv.length!=4) {
console.warn("Usage: netconf.js user@hostname command\n");
process.exit(1);
}
var hostname = process.argv[2];
var command = process.argv[3];
  1. Start up a child process in order to run the SSH client to connect to the JUNOS OS host:
  var child = child_process.spawn(
"/usr/bin/ssh", [
"auto@"+hostname,
"-q",
"-p", "830",
"-i", "JUNOS_auto_id_rsa",
"-s", "netconf"
]
);
  1. Define the important event handlers to deal with the runtime interaction with the SSH session, including handling things like reading data from the SSH session and handling error conditions:
var data="";

child.stderr.on('data', function(chunk) { process.stderr.write(chunk, "utf8"); });
child.stdout.on('data', function(chunk) {
data+=chunk;
if ((index=data.indexOf(DELIMITER))!=-1) {
var xml = data.slice(0, index);
data = data.slice(index + DELIMITER.length);
xml2js.parseString(xml, function(err, result) {
if (err) throw err;
if (result['hello']) return;
if (output=walk(result, ['rpc-reply',
'output', 0])) {
console.log(output);
} else if (config=walk(result, ['rpc-reply',
'configuration-information', 0,
'configuration-output', 0])) {
console.log(config);
} else if (error=walk(result, ['rpc-reply',
'rpc-error', 0,
'error-message', 0])) {
console.log(error);
} else {
console.log("Unexpected empty response");
}
child.stdin.end();
});
}
});

child.on('error', function(err) { console.log("SSH client error: ", err); })
  1. Finally, start the ball rolling by issuing a command RPC for the user-specified CLI command:
xmlRpcCommand(command).forEach(function(cur, ind, array) {               
child.stdin.write(cur, "utf8")
});

How it works...

Step 1 sees us prepare the runtime environment by installing a module dependency. Node.js package management system has application dependencies installed in the same directory as the application, rather than polluting system directories. This makes for a more self-contained application, but be aware that the node_modules directory in your application directory is an integral part of your application.

In step 2, we start the source code and we start by pulling in the necessary Node.js modules that we need to reference in this application. We use the child_process module to manage a child SSH session, and we use the xml2js module to do the heavy work of parsing the XML.

Step 3 defines some foundation constants. In this case, we need to use the NETCONF delimiter, as in our other applications, in order to determine where XML messages start and stop. And we also include an XML template for the command RPC that we will call.

In step 4, we create a helper routine. Because the XML parsing process will leave us with complicated JavaScript dictionaries representing each of the tags in the XML document, we want to make a nice, clean and easy syntax to walk an XML structure. Unfortunately, Node.js isn't particularly tolerant to us de-referencing dictionary elements that are non-existent. For example, if we have an object structured like this:

  routers = { 'paris--1': { version: '14.1R6', hardware: 'MX960' },
'london--1': { version: '15.1F6-S6', hardware: 'MX960' },
'frankfurt--1': { version: '15.1F6-S6', hardware: 'MX960' } }

We might look to query the software version using syntax like this:

 > routers['paris--1']['version']
'14.1R6'

Unfortunately, this fails miserably if we try to reference a device that isn't in the dictionary. Node.js throws a TypeError exception, stopping the application in its track:

> routers['amsterdam--1']['version']
TypeError: Cannot read property 'version' of undefined

Instead, we use the walk routine defined in step 4 to conditionally walk a path through a JavaScript object, returning the undefined sentinel value at the earliest failure. This allows us to deal with the error condition on an aggregate basis, rather than checking validity of every element in the path:

    > walk(routers, [ "paris--1", "version" ])
'14.1R6'
> walk(routers, [ "amsterdam--1", "version" ])
undefined

Step 5 sees us use the JavaScript dialect to parse the command-line arguments, and like the previous recipes, we simply look to glean the target hostname and the command to execute.

Then the Node.js magic is put to work in steps 6 and 7. We start off a child process, which involves the operating system forking and executing an SSH client in a similar manner to the previous recipes. But instead of interacting with the SSH client with a series of read/writes, we instead simply define event handlers for what happens in response to certain events, and let the Node.js event loop do the rest.

In our case, we deal with different events, best described in pseudo code in the following table:

Event

Description

Data is received from the SSH client's standard output

  • Read the data

  • Look for the NETCONF delimiter

  • If it's found, take all the data up to it, and try to parse it as XML

  • If it's not found, just store what we have for the next read

Data is received from the SSH client's standard error

  • Print the same data (probably an error message) to the application standard error channel

Successful XML Parse

  • Print the content of any output, configuration - output, or error-message tags

Step 8 actually solicits the output from the JUNOS OS device by emitting the RPC command which executes the user's command. When the response is received, the prepared event handlers perform their prescribed activities, which results in the output being printed.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime