[ Contents] [ Prev] [ Next] [ Index] [ Report an Error]

Tutorial: Writing Perl Client Applications

This tutorial explains how to write a Perl client application that requests operational or configuration information from the JUNOScript server or loads configuration information onto a routing platform. The following sections use the sample scripts included in the JUNOScript Perl distribution as examples:

Importing Perl Modules and Declaring 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 JUNOScript server on a routing platform. The second statement provides error checking and enforces Perl coding practices such as declaration of variables before use.

     use JUNOS::Device;
     use strict;

Include other 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:

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;

Connecting to the JUNOScript Server

The following sections explain how to use the JUNOS::Device object to connect to the JUNOScript server on a routing platform:

Satisfying Protocol Prerequisites

The JUNOScript server supports several access protocols, listed in Supported Access Protocols. For each connection to the JUNOScript server on a routing platform, 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 routing platform, creating encryption keys, or installing additional software on the routing platform or the machine where the application runs. For instructions, see Prerequisites for Establishing a Connection.

Grouping Requests

Establishing a connection to the JUNOScript server on a routing platform 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 routing platforms, you can structure the script to iterate through either the set of routing platforms or the set of requests. Keep in mind, however, that your application can effectively send only one request to one JUNOScript 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 JUNOScript server’s response to the current request.

Obtaining and Recording Parameters Required by the JUNOS::Device Object

The JUNOS::Device object takes the following required parameters, specified as keys in a Perl hash:

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:dm:x: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 router.

     Options:

       -l <login>      A login name accepted by the target router.
       -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.

     my $hostname = shift || output_usage(???);

     my $access = $opt{m} || "telnet";
     use constant VALID_ACCESSES => "telnet|ssh|clear-text|ssl";
     output_usage(???) unless (VALID_ACCESSES =~ /$access/);

     my $login = "";
     if ($opt{l}) {
         $login = $opt{l};
     } else {
         print "login: ";
         $login = ReadLine 0;
         chomp $login;
     }

     my $password = "";
     if ($opt{p}) {
         $password = $opt{p};
     } else {
         print "password: ";
         ReadMode 'noecho';
         $password = ReadLine 0;
         chomp $password;
         ReadMode 'normal';
         print "\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 four values defined by the VALID_ACCESSES constant, the script invokes the output_usage subroutine:

     my $access = $opt{m} || "telnet";
     use constant VALID_ACCESSES => "telnet|ssh|clear-text|ssl";
     output_usage(???) unless ($access =~ /VALID_ACCESSES/);

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 "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 "password: ";
         ReadMode 'noecho';
         $password = ReadLine 0;
         chomp $password;
         ReadMode 'normal';
         print "\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 JUNOScript 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 Obtaining and Recording 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 JUNOScript 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 JUNOScript 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.

     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 four 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 ($load_action =~ /VALID_ACTIONS/);

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 CLI. As described in Predefined Entity References , 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(")  => '&quot;',
         qq(>)  => '&gt;',
         qq(<)  => '&lt;',
         qq(')  => '&apos;',
         qq(&)  => '&amp;'
     );
     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 Obtaining and Recording 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 JUNOScript-specific new subroutine to create a JUNOS::Device object and establish a connection to the specified routing 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 JUNOScript Server

After establishing a connection to a JUNOScript 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 JUNOScript and JUNOS XML APIs used by the application:

See the following sections for more information:

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 can have a combination of fixed-form options, options with variable values, attributes, and a set of configuration statements. For example, the get_route_forwarding_table 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 is the recommended way to send a request to the JUNOScript server. It assumes that the $jnx variable was previously defined to be a JUNOS::Device object, as discussed in Establishing the Connection.

The following code sends a request to the JUNOScript server and handles error conditions. 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 JUNOScript server returns in response to the request (the object is defined in the lib/JUNOS/Response.pm file in the JUNOScript 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 JUNOScript 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 JUNOScript 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 platform. It is equivalent to issuing the show chassis hardware detail command.

After establishing a connection to the JUNOScript 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.

     my $res = $jnx->$query( %queryargs );
     unless ( ref $res ) {
         die "ERROR: $deviceinfo{hostname}: failed to execute command $query.\n";
     }
     my $err = $res->getFirstError(????);
     if ($err) {
         print STDERR "ERROR: $deviceinfo{'hostname'} - ", $err->{message}, "\n";
     } else {
          ...  code that uses XSLT to process results ...
     }

Example: Loading Configuration Statements

The load_configuration.pl script loads configuration statements onto a routing platform. 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

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 router ...\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 JUNOScript 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 then reads in and parses a file that contains JUNOS XML configuration tag elements or formatted ASCII statements. The name of the file was previously obtained from the command line and assigned to the $xmlfile variable. A detailed discussion of the functional subsections follows the complete code sample.

     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 the routing platform. It places the statement inside an eval block to ensure that the graceful_shutdown subroutine is invoked if the response from the JUNOScript 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 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 JUNOScript 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 JUNOScript Server

As the last step in sending a request, the application verifies that there are no errors with the response from the JUNOScript 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 JUNOScript 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 JUNOScript server into a more readable format. A detailed discussion of the functional subsections follows the complete code sample.

     my $outputfile = $opt{o} || "";
    
     my $xslfile = $opt{x} || "xsl/text.xsl";
     if ($xslfile && ! -f $xslfile) {
         die "ERROR: XSLT file $xslfile does not exist";
    
     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 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 JUNOScript Perl distribution:

The actual parsing operation begins by setting the variable $xmlfile to a filename of the form routing-platform-name.xml and invoking the printToFile function to write the JUNOScript 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 software version, such as 9.54R1 for the initial version of JUNOS Release 9.54. Because the same XSLT file can be applied to operational response tag elements from routing platforms running different versions of the JUNOS software, 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 JUNOScript 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 function 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 JUNOScript 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, the routing platform hostname, and the XML DOM tree (the configuration data) returned by the JUNOScript 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:

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 JUNOScript Server

To end the JUNOScript session and close the connection to the routing platform, 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);

[ Contents] [ Prev] [ Next] [ Index] [ Report an Error]