Tutorial: Writing Perl Client Applications
This tutorial explains how to write a Perl client application that requests operational or configuration information from the Junos XML protocol server or loads configuration information onto a device. The following sections use the sample scripts included in the Junos XML protocol Perl distribution as examples:
- Import Perl Modules and Declare Constants
- Connect to the Junos XML Protocol Server
- Submitting a Request to the Junos XML Protocol Server
- Parsing and Formatting the Response from the Junos XML Protocol Server
- Closing the Connection to the Junos XML Protocol Server
Import Perl Modules and Declare Constants
Include the following statements at the start of the application. The first statement imports the functions provided by the JUNOS::Device object, which the application uses to connect to the Junos XML protocol server on a device. The second statement provides error checking and enforces Perl coding practices such as declaration of variables before use.
use JUNOS::Device;
use strict;
Include statements to import other Perl modules as appropriate for your application. For example, several of the sample scripts import the following standard Perl modules, which include functions that handle input from the command line:
- File::Basename—Includes functions for processing filenames.
- Getopt::Std—Includes functions for reading in keyed options from the command line.
- Term::ReadKey—Includes functions for controlling terminal modes, for example suppressing onscreen echo of a typed string such as a password.
If the application uses constants, declare their values at this
point. For example, the sample diagnose_bgp.pl script includes the following statements to declare constants for
formatting output:
use constant OUTPUT_FORMAT => "%-20s%-8s%-8s%-11s%-14s%s\n";
use constant OUTPUT_TITLE =>
"\n=============== BGP PROBLEM SUMMARY ===============\n\n";
use constant OUTPUT_ENDING =>
"\n===================================================\n\n";
The load_configuration.pl script
includes the following statements to declare constants for reporting
return codes and the status of the configuration database:
use constant REPORT_SUCCESS => 1;
use constant REPORT_FAILURE => 0;
use constant STATE_CONNECTED => 1;
use constant STATE_LOCKED => 2;
use constant STATE_CONFIG_LOADED => 3;
Connect to the Junos XML Protocol Server
The following sections explain how to use the JUNOS::Device object to connect to the Junos XML protocol server on a device running Junos OS:
- Satisfying Protocol Prerequisites
- Group Requests
- Obtain and Record Parameters Required by the JUNOS::Device Object
- Obtaining Application-Specific Parameters
- Converting Disallowed Characters
- Establishing the Connection
Satisfying Protocol Prerequisites
The Junos XML protocol server supports several access protocols, listed in Supported Access Protocols. For each connection to the Junos XML protocol server on a device, the application must specify the protocol it is using. Using SSH or Secure Sockets Layer (SSL) is recommended because they provide greater security by encrypting all information before transmission across the network.
Before your application can run, you must satisfy the prerequisites for the protocol it uses. For some protocols this involves activating configuration statements on the device, creating encryption keys, or installing additional software on the device running Junos OS or the machine where the application runs. For instructions, see Prerequisites for Establishing a Connection.
Group Requests
Establishing a connection to the Junos XML protocol server on a device is one of the more time- and resource-intensive functions performed by an application. If the application sends multiple requests to a routing platform, it makes sense to send all of them within the context of one connection. If your application sends the same requests to multiple devices, you can structure the script to iterate through either the set of devices or the set of requests. Keep in mind, however, that your application can effectively send only one request to one Junos XML protocol server at a time. This is because the JUNOS::Device object does not return control to the application until it receives the closing </rpc-reply> tag that represents the end of the Junos XML protocol server’s response to the current request.
Obtain and Record Parameters Required by the JUNOS::Device Object
The JUNOS::Device object takes the following required parameters, specified as keys in a Perl hash:
- The access protocol to use when communicating with the Junos XML protocol server (key name: access). For a list of the acceptable values, see Supported Access Protocols. Before the application runs, satisfy the protocol-specific prerequisites described in Prerequisites for Establishing a Connection.
- The name of the device to which to connect (key name: hostname). For best results, specify either a fully qualified hostname or an IP address.
- The username under which to establish the connection to the Junos XML protocol server and issue requests (key name: login). The username must already exist on the specified device and have the permission bits necessary for making the requests invoked by the application.
- The password for the username (key name: password).
The sample scripts record the parameters in a Perl hash called %deviceinfo, declared as follows:
my %deviceinfo = (
'access' => $access,
'login' => $login,
'password' => $password,
'hostname' => $hostname,
);The sample scripts obtain the parameters from options entered on the command line by a user. Your application can also obtain values for the parameters from a file or database, or you can hardcode one or more of the parameters into the application code if they are constant.
Example: Collecting Parameters Interactively
Each sample script obtains the parameters required by the JUNOS::Device object from command-line options provided by the user who invokes the script. The script records the options in a Perl hash called %opt, using the getopts function defined in the Getopt::Std Perl module to read the options from the command line. (Scripts used in production environments probably do not obtain parameters interactively, so this section is important mostly for understanding the sample scripts.)
In the following example from the get_chassis_inventory.pl script, the first parameter to the getopts function defines
the acceptable options, which vary depending on the application. A
colon after the option letter indicates that it takes an argument.
The second parameter, \%opt, specifies that the values are recorded in the %opt hash. If the user does not provide at least one option, provides an invalid option, or provides the -h option, the script invokes the output_usage subroutine, which prints a usage message to the screen:
my %opt;
getopts('l:p:dx:m:o:h', \%opt) || output_usage();
output_usage() if $opt{h};
The following code defines the output_usage subroutine
for the get_chassis_inventory.pl script.
The contents of the my $usage definition and the Where and Options sections are specific to the script,
and differ for each application.
sub output_usage
{
my $usage = "Usage: $0 [options] <target>
Where:
<target> The hostname of the target device.
Options:
-l <login> A login name accepted by the target device.
-p <password> The password for the login name.
-m <access> Access method. It can be clear-text, ssl, ssh or telnet.
Default: telnet.
-x <format> The name of the XSL file to display the response.
Default: xsl/chassis_inventory_csv.xsl
-o <filename> File to which to write output, instead of standard output.
-d Turn on debugging.\n\n";
die $usage;
}
The get_chassis_inventory.pl script includes the following code to obtain values from the command
line for the four parameters required by the JUNOS::Device object. A detailed discussion of the various functional units
follows the complete code sample.
# Get the hostname
my $hostname = shift || output_usage();
# Get the access method
my $access = $opt{m} || "telnet";
use constant VALID_ACCESSES => "telnet|ssh|clear-text|ssl";
output_usage() unless (VALID_ACCESSES =~ /$access/);
# Check for login name. If not provided, prompt for it
my $login = "";
if ($opt{l}) {
$login = $opt{l};
} else {
print STDERR "login: ";
$login = ReadLine 0;
chomp $login;
}
# Check for password. If not provided, prompt for it
my $password = "";
if ($opt{p}) {
$password = $opt{p};
} else {
print STDERR "password: ";
ReadMode 'noecho';
$password = ReadLine 0;
chomp $password;
ReadMode 'normal';
print STDERR "\n";
}
In the first line of the preceding code sample, the script uses the Perl shift function to read the hostname from the end of the command line. If the hostname is missing, the script invokes the output_usage subroutine to print the usage message, which specifies that a hostname is required:
my $hostname = shift || output_usage();
The script next determines which access protocol to use, setting the $access variable to the value of the -m command-line option or to the value telnet if the -m option is not provided. If the specified value does not match one of the values defined by the VALID_ACCESSES constant, the script invokes the output_usage subroutine to print the usage message.
my $access = $opt{m} || "telnet";
use constant VALID_ACCESSES => "telnet|ssh|clear-text|ssl";
output_usage() unless (VALID_ACCESSES =~ /$access/);The script then determines the username, setting the $login variable to the value of the -l command-line option. If the option is not provided, the script prompts for it and uses the ReadLine function (defined in the standard Perl Term::ReadKey module) to read it from the command line:
my $login = "";
if ($opt{l}) {
$login = $opt{l};
} else {
print STDERR "login: ";
$login = ReadLine 0;
chomp $login;
}The script finally determines the password for the username, setting the $password variable to the value of the -p command-line option. If the option is not provided, the script prompts for it. It uses the ReadMode function (defined in the standard Perl Term::ReadKey module) twice: first to prevent the password from echoing visibly on the screen and then to return the shell to normal (echo) mode after it reads the password:
my $password = "";
if ($opt{p}) {
$password = $opt{p};
} else {
print STDERR "password: ";
ReadMode 'noecho';
$password = ReadLine 0;
chomp $password;
ReadMode 'normal';
print STDERR "\n";
}
Obtaining Application-Specific Parameters
In addition to the parameters required by the JUNOS::Device object, applications might need to define other parameters, such as the name of the file to which to write the data returned by the Junos XML protocol server in response to a request, or the name of the Extensible Stylesheet Transformation Language (XSLT) file to use for transforming the data.
As with the parameters required by the JUNOS::Device object, your application can hardcode the values in the application code, obtain them from a file, or obtain them interactively. The sample scripts obtain values for these parameters from command-line options in the same manner as they obtain the parameters required by the JUNOS::Device object (discussed in Obtain and Record Parameters Required by the JUNOS::Device Object). Several examples follow.
The following line enables a debugging trace if the user includes the -d command-line option. It invokes the JUNOS::Trace::init routine defined in the JUNOS::Trace module, which is already imported with the JUNOS::Device object.
JUNOS::Trace::init(1) if $opt{d};
The following line sets the $outputfile variable to the value specified by the -o command-line option. It names the local file to which the Junos XML protocol server’s response is written. If the -o option is not provided, the variable is set to the empty string.
my $outputfile = $opt{o} || “ “ ;
The following code from the diagnose_bgp.pl script defines which XSLT file to use to transform the Junos XML
protocol server’s response. The first line sets the $xslfile variable to the value specified by the -x command-line
option. If the option is not provided, the script uses the text.xsl file supplied with the script, which transforms
the data to ASCII text. The if statement verifies that the
specified XSLT file exists; the script terminates if it does not.
# Retrieve the XSLT file, default is parsed by perl
my $xslfile = $opt{x} || "xsl/text.xsl";
if ($xslfile && ! -f $xslfile) {
die "ERROR: XSLT file $xslfile does not exist";
}The following code from the load_configuration.pl script defines whether to merge, replace, update, or overwrite the
new configuration data into the configuration database (for more information
about these operations, see Changing Configuration Information). The
first two lines set the $load_action variable to the value
of the -a command-line option, or to the default value merge if the option is not provided. If the specified value
does not match one of the valid actions defined in the third line,
the script invokes the output_usage subroutine.
# The default action for load_configuration is 'merge'
my $load_action = "merge";
$load_action = $opt{a} if $opt{a};
use constant VALID_ACTIONS => "merge|replace|override";
output_usage() unless (VALID_ACTIONS =~ /$load_action/);Converting Disallowed Characters
Scripts that handle configuration data usually accept and output the data either as Junos XML tag elements or as formatted ASCII statements like those used in the Junos OS CLI. As described in XML and Junos XML Management Protocol Conventions Overview, certain characters cannot appear in their regular form in an XML document. These characters include the apostrophe ( ‘ ), the ampersand ( & ), the greater-than ( > ) and less-than ( < ) symbols, and the quotation mark ( " ). Because these characters might appear in formatted ASCII configuration statements, the script must convert the characters to the corresponding predefined entity references.
The load_configuration.pl script
uses the get_escaped_text subroutine to substitute predefined
entity references for disallowed characters (the get_configuration.pl script includes similar code). The script first defines the mappings
between the disallowed characters and predefined entity references,
and sets the variable $char_class to a regular expression
that contains all of the entity references, as follows:
my %escape_symbols = (
qq(") => '"',
qq(>) => '>',
qq(<) => '<',
qq(') => ''',
qq(&) => '&'
);
my $char_class = join ("|", map { "($_)" } keys %escape_symbols);
The following code defines the get_escaped_text subroutine
for the load_configuration.pl script.
A detailed discussion of the subsections in the routine follows the
complete code sample.
sub get_escaped_text
{
my $input_file = shift;
my $input_string = "";
open(FH, $input_file) or return undef;
while(<FH>) {
my $line = $_;
$line =~ s/<configuration-text>//g;
$line =~ s/<\/configuration-text>//g;
$line =~ s/($char_class)/$escape_symbols{$1}/ge;
$input_string .= $line;
}
return "<configuration-text>$input_string</configuration-text>";
}
The first subsection of the preceding code sample reads in a file containing formatted ASCII configuration statements:
sub get_escaped_text
{
my $input_file = shift;
my $input_string = "";
open(FH, $input_file) or return undef;
In the next subsection, the subroutine temporarily discards the lines that contain the opening <get-configuration> and closing </get-configuration> tags, then replaces the disallowed characters on each remaining line with predefined entity references and appends the line to the $input_string variable:
while(<FH>) {
my $line = $_;
$line =~ s/<configuration-text>//g;
$line =~ s/<\/configuration-text>//g;
$line =~ s/($char_class)/$escape_symbols{$1}/ge;
$input_string .= $line;
}
The subroutine concludes by replacing the opening <get-configuration> and closing </get-configuration> tags, and returning the converted set of statements:
return "<configuration-text>$input_string</configuration-text>"; }
Establishing the Connection
After obtaining values for the parameters required for the JUNOS::Device object (see Obtain and Record Parameters Required by the JUNOS::Device Object), each sample script records them in the %deviceinfo hash:
my %deviceinfo = (
access => $access,
login => $login,
password => $password,
hostname => $hostname,
);
The script then invokes the Junos XML protocol-specific new subroutine to create a JUNOS::Device object and establish a connection to the specified routing, switching, or security platform. If the connection attempt fails (as tested by the ref operator), the script exits.
my $jnx = new JUNOS::Device(%deviceinfo);
unless ( ref $jnx ) {
die "ERROR: $deviceinfo{hostname}: failed to connect.\n";
Submitting a Request to the Junos XML Protocol Server
After establishing a connection to a Junos XML protocol server (see Establishing the Connection), your application can submit one or more requests by invoking the Perl methods that are supported in the version of the Junos XML protocol and Junos XML API used by the application:
- Each version of software supports a set of methods that correspond to CLI operational mode commands (later releases generally support more methods). For a list of the operational methods supported in the current version, see Mapping CLI Commands to Perl Methods and the files stored in the lib/JUNOS/release directory of the Junos XML protocol Perl distribution (release is the Junos OS version code, such as 10.3R1 for the initial version of Junos OS Release 10.3). The files have names in the format package_methods.pl, where package is a software package.
- The set of methods that correspond to operations on configuration
objects is defined in the
lib/JUNOS/Methods.pmfile in the Junos XML protocol Perl distribution. For more information about configuration operations, see Changing Configuration Information and Summary of Junos XML Protocol Tag Elements.
See the following sections for more information:
- Providing Method Options or Attributes
- Submitting a Request
- Example: Getting an Inventory of Hardware Components
- Example: Loading Configuration Statements
Providing Method Options or Attributes
Many Perl methods have one or more options or attributes. The
following list describes the notation used to define a method’s
options in the lib/JUNOS/Methods.pm and lib/JUNOS/release/package_methods.pl files, and the notation that an application
uses when invoking the method:
- A method without options is defined as $NO_ARGS, as in the following entry for the get_system_uptime_information method:
## Method : <get-system-uptime-information> ## Returns: <system-uptime-information> ## Command: "show system uptime" get_system_uptime_information => $NO_ARGS,
To invoke a method without options, follow the method name with an empty set of parentheses as in the following example:
$jnx->get_system_uptime_information( );
- A fixed-form option is defined as type $TOGGLE. In the following example, the get_software_information method takes two fixed-form options, brief and detail:
## Method : <get-software-information> ## Returns: <software-information> ## Command: "show version" get_software_information => brief => $TOGGLE, detail => $TOGGLE, },To include a fixed-form option when invoking a method, set it to the value 1 (one) as in the following example:
$jnx->get_software_information(brief => 1);
- An option with a variable value is defined as type $STRING. In the following example, the get_cos_drop_profile_information method takes the profile_name argument:
## Method : <get-cos-drop-profile-information> ## Returns: <cos-drop-profile-information> ## Command: "show class-of-service drop-profile" get_cos_drop_profile_information => { profile_name => $STRING, },To include a variable value when invoking a method, enclose the value in single quotes as in the following example:
$jnx->get_cos_drop_profile_information(profile_name => 'user-drop-profile');
- An attribute is defined as type $ATTRIBUTE. In
the following example, the load_configuration method takes
the rollback attribute:
load_configuration => { rollback => $ATTRIBUTE },To include a numerical attribute value when invoking a method, set it to the appropriate value. The following example rolls the candidate configuration back to the previous configuration that has an index of 2:
$jnx->load_configuration(rollback => 2);
To include a string attribute value when invoking a method, enclose the value in single quotes as in the following example:
$jnx->get_configuration(format => ‘text’);
- A set of configuration statements or corresponding tag
elements is defined as type $DOM. In the following example,
the get_configuration method takes a set of configuration
statements (along with two attributes):
get_configuration => { configuration => $DOM, format => $ATTRIBUTE, database => $ATTRIBUTE, },To include a set of configuration statements when invoking a method, provide a parsed set of statements or tag elements. The following example refers to a set of Junos XML configuration tag elements in the
config-input.xmlfile. For further discussion, see Example: Loading Configuration Statements.my $parser = new XML::DOM::Parser; $jnx->load_configuration( format => ‘xml’, action => ‘merge’, configuration => $parser->parsefile(config-input.xml) );
A method can have a combination of fixed-form options, options with variable values, attributes, and a set of configuration statements. For example, the get_forwarding_table_information method has four fixed-form options and five options with variable values:
## Method : <get-forwarding-table-information>
## Returns: <forwarding-table-information>
## Command: "show route forwarding-table"
get_forwarding_table_information => {
detail => $TOGGLE,
extensive => $TOGGLE,
multicast => $TOGGLE,
family => $STRING,
vpn => $STRING,
summary => $TOGGLE,
matching => $STRING,
destination => $STRING,
label => $STRING,
},
Submitting a Request
The following code is the recommended way to send a request to the Junos XML protocol server and shows how to handle error conditions. The $jnx variable is defined to be a JUNOS::Device object, as discussed in Establishing the Connection. A detailed discussion of the functional subsections follows the complete code sample.
my %arguments = ( );
%arguments = ( argument1 => value1 ,
argument2 => value2 , ...);
argument3 => value3 ,
...);
my $res = $jnx-> method (%args);
unless ( ref $res ) {
$jnx->request_end_session( );
$jnx->disconnect( );
print "ERROR: Could not send request to $hostname\n";
}
my $err = $res->getFirstError( );
if ($err) {
$jnx->request_end_session( );
$jnx->disconnect();
print "ERROR: Error for $hostname: " . $err->{message} . "\n";
}
The first subsection of the preceding code sample creates a hash called %arguments to define values for a method’s options or attributes. For each argument, the application uses the notation described in Providing Method Options or Attributes.
my %arguments = ( );
%arguments = ( argument1 => value1 ,
argument2 => value2 , ...);
argument3 => value3 ,
...);
The application then invokes the method, defining the $res variable to point to the JUNOS::Response object that the
Junos XML protocol server returns in response to the request (the
object is defined in the lib/JUNOS/Response.pm file in the Junos XML protocol Perl distribution):
my $res = $jnx-> method (%args);
If the attempt to send the request failed, the application prints an error message and closes the connection:
unless ( ref $res ) {
$jnx->request_end_session();
$jnx->disconnect();
print "ERROR: Could not send request to $hostname\n";
}
If there was an error in the Junos XML protocol server’s
response, the application prints an error message and closes the connection.
The getFirstError function is defined in the JUNOS::Response module (lib/JUNOS/Response.pm)
in the Junos XML protocol Perl distribution.
my $err = $res->getFirstError( );
if ($err) {
$jnx->request_end_session( );
$jnx->disconnect( );
print "ERROR: Error for $hostname: " . $err->{message} . "\n";
}
Example: Getting an Inventory of Hardware Components
The get_chassis_inventory.pl script retrieves and displays a detailed inventory of the hardware
components installed in a routing, switching, or security platform.
It is equivalent to issuing the show chassis hardware detail command.
After establishing a connection to the Junos XML protocol server, the script defines get_chassis_inventory as the request to send and includes the detail argument:
my $query = "get_chassis_inventory"; my %queryargs = ( detail => 1 );
The script sends the query and assigns the results to the $res variable. It performs two tests on the results, and prints an error message if it cannot send the request or if errors occurred when executing it. If no errors occurred, the script uses XSLT to transform the results. For more information, see Parsing and Formatting an Operational Response.
# send the command and receive a XML::DOM object
my $res = $jnx->$query( %queryargs );
unless ( ref $res ) {
die "ERROR: $deviceinfo{hostname}: failed to execute command $query.\n";
}
# Check and see if there were any errors in executing the command.
my $err = $res->getFirstError( );
if ($err) {
print STDERR "ERROR: $deviceinfo{'hostname'} - ", $err->{message}, "\n";
} else {
# Now do the transformation using XSLT
... code that uses XSLT to process results ...
}
Example: Loading Configuration Statements
The load_configuration.pl script
loads configuration statements onto a device. It uses the basic structure
for sending requests described in Submitting a
Request but also defines a graceful_shutdown subroutine that handles
errors in a slightly more elaborate manner than that described in Submitting a
Request. The following sections describe the different
functions that the script performs:
- Handling Error Conditions
- Locking the Configuration
- Reading In and Parsing the Configuration Data
- Loading the Configuration Data
- Committing the Configuration
Handling Error Conditions
The graceful_shutdown subroutine in the load_configuration.pl script handles errors in a
slightly more elaborate manner than the generic structure described
in Submitting a
Request. It employs the following additional
constants:
use constant REPORT_SUCCESS => 1; use constant REPORT_FAILURE => 0; use constant STATE_CONNECTED => 1; use constant STATE_LOCKED => 2; use constant STATE_CONFIG_LOADED => 3;
The first two if statements in the subroutine refer
to the STATE_CONFIG_LOADED and STATE_LOCKED conditions,
which apply specifically to loading a configuration in the load_configuration.pl script. The if statement
for STATE_CONNECTED is similar to the error checking described
in Submitting a
Request. The eval statement
used in each case ensures that any errors that occur during execution
of the enclosed function call are trapped so that failure of the function
call does not cause the script to exit.
sub graceful_shutdown
{
my ($jnx, $req, $state, $success) = @_;
if ($state >= STATE_CONFIG_LOADED) {
print "Rolling back configuration ...\n";
eval {
$jnx->load_configuration(rollback => 0);
};
}
if ($state >= STATE_LOCKED) {
print "Unlocking configuration database ...\n";
eval {
$jnx->unlock_configuration();
};
}
if ($state >= STATE_CONNECTED) {
print "Disconnecting from the device ...\n";
eval {
$jnx->request_end_session()
$jnx->disconnect();
};
}
if ($success) {
die "REQUEST $req SUCCEEDED\n";
} else {
die "REQUEST $req FAILED\n";
};
}
Locking the Configuration
The main section of the load_configuration.pl script begins by establishing a connection to a Junos XML protocol
server, as described in Establishing
the Connection. It then
invokes the lock_configuration method to lock the configuration
database. In case of error, the script invokes the graceful_shutdown subroutine described in Handling Error Conditions.
print "Locking configuration database ...\n";
my $res = $jnx->lock_configuration();
my $err = $res->getFirstError();
if ($err) {
print "ERROR: $deviceinfo{hostname}: failed to lock configuration. Reason: $err->{message}.\n";
graceful_shutdown($jnx, $xmlfile, STATE_CONNECTED, REPORT_FAILURE);
}
Reading In and Parsing the Configuration Data
In the following code sample, the load_configuration.pl script reads in and parses a file that contains Junos XML configuration
tag elements or ASCII-formatted statements. A detailed discussion
of the functional subsections follows the complete code sample.
# Load the configuration from the given XML file
print "Loading configuration from $xmlfile ...\n";
if (! -f $xmlfile) {
print "ERROR: Cannot load configuration in $xmlfile\n";
graceful_shutdown($jnx, $xmlfile, STATE_LOCKED, REPORT_FAILURE);
}
my $parser = new XML::DOM::Parser;
...
my $doc;
if ($opt{t}) {
my $xmlstring = get_escaped_text($xmlfile);
$doc = $parser->parsestring($xmlstring) if $xmlstring;
} else {
$doc = $parser->parsefile($xmlfile);
}
unless ( ref $doc ) {
print "ERROR: Cannot parse $xmlfile, check to make sure the XML data is well-formed\n";
graceful_shutdown($jnx, $xmlfile, STATE_LOCKED, REPORT_FAILURE);
}
The first subsection of the preceding code sample verifies the existence of the file containing configuration data. The name of the file was previously obtained from the command line and assigned to the $xmlfile variable. If the file does not exist, the script invokes the graceful_shutdown subroutine:
print "Loading configuration from $xmlfile ...\n";
if (! -f $xmlfile) {
print "ERROR: Cannot load configuration in $xmlfile\n";
graceful_shutdown($jnx, $xmlfile, STATE_LOCKED, REPORT_FAILURE);
}
If the -t command-line option was included when the load_configuration.pl script was invoked, the file
referenced by the $xmlfile variable should contain formatted
ASCII configuration statements like those returned by the CLI configuration-mode show command. The script invokes the get_escaped_text subroutine described in Converting Disallowed
Characters, assigning
the result to the $xmlstring variable. The script invokes
the parsestring function to transform the data in the file
into the proper format for loading into the configuration hierarchy,
and assigns the result to the $doc variable. The parsestring function is defined in the XML::DOM::Parser module, and
the first line in the following sample code instantiates the module
as an object, setting the $parser variable to refer to it:
my $parser = new XML::DOM::Parser;
...
my $doc;
if ($opt{t}) {
my $xmlstring = get_escaped_text($xmlfile);
$doc = $parser->parsestring($xmlstring) if $xmlstring;
If the file contains Junos XML configuration tag elements instead, the script invokes the parsefile function (also defined in the XML::DOM::Parser module) on the file:
} else {
$doc = $parser->parsefile($xmlfile);
}
If the parser cannot transform the file, the script invokes the graceful_shutdown subroutine described in Handling Error Conditions:
unless ( ref $doc ) {
print "ERROR: Cannot parse $xmlfile, check to make sure the XML data is well-formed\n";
graceful_shutdown($jnx, $xmlfile, STATE_LOCKED, REPORT_FAILURE);
}
Loading the Configuration Data
The script now invokes the load_configuration method to load the configuration onto thedevice. It places the statement inside an eval block to ensure that the graceful_shutdown subroutine is invoked if the response from the Junos XML protocol server has errors.
eval {
$res = $jnx->load_configuration(
format => $config_format,
action => $load_action,
configuration => $doc);
};
if ($@) {
print "ERROR: Failed to load the configuration from $xmlfile. Reason: $@\n";
graceful_shutdown($jnx, $xmlfile, STATE_CONFIG_LOADED, REPORT_FAILURE);
exit(1);
}
The variables used to define the method’s three arguments were set at previous points in the application file:
- The $config_format variable was previously set
to xml unless the -t command-line option was included:
my $config_format = "xml"; $config_format = "text" if $opt{t}; - The $load_action variable was previously set
to merge unless the -a command-line option was included.
The final two lines verify that the specified value is one of the
acceptable choices:
my $load_action = "merge"; $load_action = $opt{a} if $opt{a}; use constant VALID_ACTIONS => "merge|replace|override"; output_usage() unless ( $load_action =~ /VALID_ACTIONS/); - The $doc variable was set to the output from the parsestring or parsefile function (defined in the XML::DOM::Parser module), as described in Reading In and Parsing the Configuration Data.
The script performs two additional checks for errors and invokes the graceful_shutdown subroutine in either case:
unless ( ref $res ) {
print "ERROR: Failed to load the configuration from $xmlfile\n";
graceful_shutdown($jnx, $xmlfile, STATE_LOCKED, REPORT_FAILURE);
}
$err = $res->getFirstError();
if ($err) {
print "ERROR: Failed to load the configuration. Reason: $err->{message}\n";
graceful_shutdown($jnx, $xmlfile, STATE_CONFIG_LOADED, REPORT_FAILURE);
}
Committing the Configuration
If there are no errors, the script invokes the commit_configuration method (defined in the file lib/JUNOS/Methods.pm in the Junos XML protocol Perl distribution):
print "Committing configuration from $xmlfile ...\n";
$res = $jnx->commit_configuration();
$err = $res->getFirstError();
if ($err) {
print "ERROR: Failed to commit configuration. Reason: $err->{message}.\n";
graceful_shutdown($jnx, $xmlfile, STATE_CONFIG_LOADED, REPORT_FAILURE);
}
Parsing and Formatting the Response from the Junos XML Protocol Server
As the last step in sending a request, the application verifies that there are no errors with the response from the Junos XML protocol server (see Submitting a Request). It can then write the response to a file, to the screen, or both. If the response is for an operational query, the application usually uses XSLT to transform the output into a more readable format, such as HTML or formatted ASCII text. If the response consists of configuration data, the application can store it as XML (the Junos XML tag elements generated by default from the Junos XML protocol server) or transform it into formatted ASCII text.
The following sections discuss parsing and formatting options:
Parsing and Formatting an Operational Response
The following code sample from the diagnose_bgp.pl and get_chassis_inventory.pl scripts
uses XSLT to transform an operational response from the Junos XML
protocol server into a more readable format. A detailed discussion
of the functional subsections follows the complete code sample.
# Get the name of the output file
my $outputfile = $opt{o} || "";
# Retrieve the XSLT file
my $xslfile = $opt{x} || "xsl/text.xsl";
if ($xslfile && ! -f $xslfile) {
die "ERROR: XSLT file $xslfile does not exist";
#Get the xmlfile
my $xmlfile = "$deviceinfo{hostname}.xml";
$res->printToFile($xmlfile);
my $nm = $res->translateXSLtoRelease('xmlns:lc', $xslfile, "$xslfile.tmp");
if ($nm) {
print "Transforming $xmlfile with $xslfile...\n" if $outputfile;
my $command = "xsltproc $nm $deviceinfo{hostname}.xml";
$command .= "> $outputfile" if $outputfile;
system($command);
print "Done\n" if $outputfile;
print "See $outputfile\n" if $outputfile;
}
else {
print STDERR "ERROR: Invalid XSL file $xslfile\n";
}
The first line of the preceding code sample illustrates how the scripts read the -o option from the command line to obtain the name of the file into which to write the results of the XSLT transformation:
my $outputfile = $opt{o} || "";
From the -x command-line option, the scripts obtain
the name of the XSLT file to use, setting a default value if
the option is not provided. The scripts exit if the specified
file does not exist. The following example is from the diagnose_bgp.pl script:
my $xslfile = $opt{x} || "xsl/text.xsl";
if ($xslfile && ! -f $xslfile) {
die "ERROR: XSLT file $xslfile does not exist";
For examples of XSLT files, see the following directories in the Junos XML protocol Perl distribution:
- The
examples/diagnose_bpg/xsldirectory contains XSLT files for thediagnose_bpg.plscript:dhtml.xslgenerates dynamic HTML,html.xslgenerates HTML, and text.xsl generates ASCII text. - The
examples/get_chassis_inventory/xsldirectory contains XSLT files for theget_chassis_inventory.plscript:chassis_inventory_csv.xslgenerates a list of comma-separated values,chassis_inventory_html.xslgenerates HTML, andchassis_inventory_xml.xslgenerates XML.
The actual parsing operation begins by setting the variable $xmlfile to a filename of the form device-name.xml and invoking the printToFile function to write the Junos XML protocol server’s response into the file (the printToFile function is defined in the XML::DOM::Parser module):
my $xmlfile = "$deviceinfo{hostname}.xml";
$res->printToFile($xmlfile);
The next line invokes the translateXSLtoRelease function (defined in the Junos::Response module) to alter one of the namespace definitions in the XSLT file. This is necessary because the XSLT 1.0 specification requires that every XSLT file define a specific value for each default namespace used in the data being transformed. The xmlns attribute in a Junos XML operational response tag element includes a code representing the Junos OS version, such as 10.3R1 for the initial version of Junos OS Release 10.3. Because the same XSLT file can be applied to operational response tag elements from devices running different versions of the Junos OS, the XSLT file cannot predefine an xmlns namespace value that matches all versions. The translateXSLtoRelease function alters the namespace definition in the XSLT file identified by the $xslfile variable to match the value in the Junos XML protocol server’s response. It assigns the resulting XSLT file to the $nm variable.
my $nm = $res->translateXSLtoRelease('xmlns:lc', $xslfile, "$xslfile.tmp");
After verifying that the translateXSLtoRelease function succeeded, the invokes the format_by_xslt, which builds a command string and assigns it to the $command variable. The first part of the command string invokes the xsltproc command and specifies the names of the XSLT and configuration data files ($nm and $deviceinfo{hostname}.xml):
if ($nm) {
print "Transforming $xmlfile with $xslfile...\n" if $outputfile;
my $command = "xsltproc $nm $deviceinfo{hostname}.xml";
If the $outputfile variable is defined (the file for storing the result of the XSLT transformation exists), the script appends a string to the $command variable to write the results of the xsltproc command to the file. (If the file does not exist, the script writes the results to standard out [stdout].) The script then invokes the system function to execute the command string and prints status messages to stdout.
$command .= "> $outputfile" if $outputfile; system($command); print "Done\n" if $outputfile; print "See $outputfile\n" if $outputfile; }
If the translateXSLtoRelease function fails (the if ($nm) expression evaluates to “ false"), the script prints an error:
else {
print STDERR "ERROR: Invalid XSL file $xslfile\n";
}
Parsing and Outputting Configuration Data
The get_config.pl script uses
the outconfig subroutine to write the configuration data
obtained from the Junos XML protocol server to a file either as Junos
XML tag elements or as formatted ASCII text.
The outconfig subroutine takes four parameters. Three must have defined values: the directory in which to store the output file, device hostname, and the XML DOM tree (the configuration data) returned by the Junos XML protocol server. The fourth parameter indicates whether to output the configuration as formatted ASCII text, and has a null value if the requested output is Junos XML tag elements. In the following code sample, the script obtains values for the four parameters and passes them to the outconfig subroutine. A detailed discussion of each line follows the complete code sample.
my(%opt,$login,$password);
getopts('l:p:dm:hit', \%opt) || output_usage();
output_usage() if $opt{h};
my $basepath = shift || output_usage;
my $hostname = shift || output_usage;
my $config = getconfig( $hostname, $jnx, $opt{t} );
outconfig( $basepath, $hostname, $config, $opt{t} );
In the first lines of the preceding sample code, the get_config.pl script uses the following statements
to obtain values for the four parameters to the outconfig subroutine:
- If the user provides the -t option on the command
line, the getopts subroutine records it in the %opt hash. The value keyed to $opt{t} is passed as the fourth
parameter to the outconfig subroutine. (For more information
about reading options from the command line, see Example: Collecting Parameters Interactively.)
getopts('l:p:dm:hit', \%opt) || output_usage(); - The following line reads the first element of the command
line that is not an option preceded by a hyphen. It assigns the value
to the $basepath variable, defining the name of the directory
in which to store the file containing the output from the outconfig subroutine. The variable value is passed as the first parameter
to the outconfig subroutine.
my $basepath = shift || output_usage;
- The following line reads the next element on the command
line. It assigns the value to the $hostname variable, defining
the routing, switching, or security device hostname. The variable
value is passed as the second parameter to the outconfig subroutine.
my $hostname = shift || output_usage;
- The following line invokes the getconfig subroutine
to obtain configuration data from the Junos XML protocol server on
the specified device, assigning the resulting XML DOM tree to the $config variable. The variable value is passed as the third
parameter to the outconfig subroutine.
my $config = getconfig( $hostname, $jnx, $opt{t} );
The following code sample invokes and defines the outconfig subroutine. A detailed discussion of each functional subsection in the subroutine follows the complete code sample.
outconfig( $basepath, $hostname, $config, $opt{t} );
sub outconfig( $$$$ ) {
my $leader = shift;
my $hostname = shift;
my $config = shift;
my $text_mode = shift;
my $trailer = "xmlconfig";
my $filename = $leader . "/" . $hostname . "." . $trailer;
print "# storing configuration for $hostname as $filename\n";
my $config_node;
my $top_tag = "configuration";
$top_tag .= "-text" if $text_mode;
if ($config->getTagName() eq $top_tag) {
$config_node = $config;
} else {
print "# unknown response component ", $config->getTagName(), "\n";
}
if ( $config_node && $config_node ne "" ) {
if ( open OUTPUTFILE, ">$filename" ) {
if (!$text_mode) {
print OUTPUTFILE "<?xml version=\"1.0\"?>\n";
print OUTPUTFILE $config_node->toString(), "\n";
} else {
my $buf = $config_node->getFirstChild()->toString();
$buf =~ s/($char_class)/$escapes{$1}/ge;
print OUTPUTFILE "$buf\n";
}
close OUTPUTFILE;
}
else {
print "ERROR: could not open output file $filename\n";
}
}
else {
print "ERROR: empty configuration data for $hostname\n";
}
}
The first lines of the outconfig subroutine read in the four parameters passed in when the subroutine is invoked, assigning each to a local variable:
outconfig( $basepath, $hostname, $config, $opt{t} );
sub outconfig( $$$$ ) {
my $leader = shift;
my $hostname = shift;
my $config = shift;
my $text_mode = shift;
The subroutine constructs the name of the file to which to write the subroutine’s output and assigns the name to the $filename variable. The filename is constructed from the first two parameters (the directory name and hostname) and the $trailer variable, resulting in a name of the form directory-name/hostname.xmlconfig:
my $trailer = "xmlconfig";
my $filename = $leader . "/" . $hostname . "." . $trailer;
print "# storing configuration for $hostname as $filename\n";
The subroutine checks that the first tag in the XML DOM tree correctly indicates the type of configuration data in the file. If the user included the -t option on the command line, the first tag should be <configuration-text> because the file contains formatted ASCII configuration statements; otherwise, the first tag should be <configuration> because the file contains Junos XML tag elements. The subroutine sets the $top_tag variable to the appropriate value depending on the value of the $text_mode variable (which takes its value from opt{t}, passed as the fourth parameter to the subroutine). The subroutine invokes the getTagName function (defined in the XML::DOM::Element module) to retrieve the name of the first tag in the input file, and compares the name to the value of the $top_tag variable. If the comparison succeeds, the XML DOM tree is assigned to the $config_node variable. Otherwise, the subroutine prints an error message because the XML DOM tree is not valid configuration data.
my $config_node;
my $top_tag = "configuration";
$top_tag .= "-text" if $text_mode;
if ($config->getTagName( ) eq $top_tag) {
$config_node = $config;
} else {
print "# unknown response component ", $config->getTagName( ), "\n";
}
The subroutine then uses several nested if statements. The first if statement verifies that the XML DOM tree exists and contains data:
if ( $config_node && $config_node ne "" ) {
... actions if XML DOM tree contains data ...
}
else {
print "ERROR: empty configuration data for $hostname\n";
}
If the XML DOM tree contains data, the subroutine verifies that the output file can be opened for writing:
if ( open OUTPUTFILE, ">$filename" ) {
... actions if output file is writable ...
}
else {
print "ERROR: could not open output file $filename\n";
}
If the output file can be opened for writing, the script writes the configuration data into it. If the user requested Junos XML tag elements—the user did not include the -t option on the command line, so the $text_mode variable does not have a value—the script writes the string <?xml version=1.0?> as the first line in the output file, and then invokes the toString function (defined in the XML::DOM module) to write each Junos XML tag element in the XML DOM tree on a line in the output file:
if (!$text_mode) {
print OUTPUTFILE "<?xml version=\"1.0\"?>\n";
print OUTPUTFILE $config_node->toString( ), "\n";
If the user requested formatted ASCII text, the script invokes the getFirstChild and toString functions (defined in the XML::DOM module) to write the content of each tag on its own line in the output file. The script substitutes predefined entity references for disallowed characters (which are defined in the %escapes hash), writes the output to the output file, and closes the output file. (For information about defining the %escapes hash to contain the set of disallowed characters, see Converting Disallowed Characters.)
} else {
my $buf = $config_node->getFirstChild( )->toString( );
$buf =~ s/($char_class)/$escapes{$1}/ge;
print OUTPUTFILE "$buf\n";
}
close OUTPUTFILE;
Closing the Connection to the Junos XML Protocol Server
To end the Junos XML protocol session and close the connection to the device, each sample script invokes the request_end_session and disconnect methods. Several of the scripts do this in standalone statements:
$jnx->request_end_session( ); jnx->disconnect();
The load_configuration.pl script
invokes the graceful_shutdown subroutine instead (for more
information, see Handling Error Conditions):
graceful_shutdown($jnx, $xmlfile, STATE_LOCKED, REPORT_SUCCESS);
Hide Navigation Pane
Show Navigation Pane
Download
SHA1