Chapter 7 - Operational Mode Examples

UI Infrastructure Programmer's Guide

Chapter 7 - Operational Command Examples

In this chapter, the implementation of the operational mode command 'show jnx-example statistics' is examined in section 7.1., followed by augmenting the 'show jnx-example' tree to include the 'show jnx-example user' command in section 7.2.

7.1. 'show jnx-example statistics'

7.1.1. DDL input

...src/junos/lib/ddl/input/jnx-example.cmd.dd

        #define DNAME_JNX_EXAMPLED "jnx-exampled"

        require-names jnx-example;

        command juniper-command {

            /*                                               */
            /* Example service commands                      */
            /*                                               */
            command show {
                command jnx-example {
                    require jnx-example;
                    help "Show example service information";

                    command statistics {
                        help "Show statistics";
                        action execute DNAME_JNX_EXAMPLED;
                    }
                }
            }
        }

This DDL input implements the 'show jnx-example statistics' operational command. It looks simple enough. Basically, mgd is being instructed to pass this operational command to mgd to process the command - this is achieved by 'action execute DNAME_JNX_EXAMPLED'.

7.1.2. Output

What does jnx-exampled show when users executes the operational command 'show jnx-example statistics'?

        user@router> show jnx-example statistics
        show example statistics 
        System Statistics:
          CPU Usage: 3%
          Memory Usage: 125424 KB

The xml output:

        user@router> show jnx-example statistics | display xml
        <rpc-reply xmlns:junos="http://xml.juniper.net/junos/8.2R1/junos">
            <exampled-statistics>
                <cpu-usage>3</cpu-usage>
                <memory-usage>125424</memory-usage>
            </exampled-statistics>
        </rpc-reply>

So, the statistics being shown are the cpu usage and memory usage on this JUNOS device.

Note that these statistics has nothing to do with any of the configuration settings under the jnx-example hierachy. It is simply a function jnx-example users are interested.

7.1.3. jnx-example code for 'show jnx-example statistics'

There is usually a program file for each daemon to handle UI operational commands. For jnx-example, that file is jnx-example_ui.c. In this file, it contains the menu structure of all possible UI operational commands, and the functions the menu calls when the commands are executed.

Below is the menu structure that traverses down to 'show jnx-example statistics'.

/*****************************************************************************/
/*                        Menu Structure                                     */
/*****************************************************************************/

/*                                                                           */
/* format:                                                                   */
/*   command, desc, arg block(or 0), child menu(or 0), command(or 0)         */
/*                                                                           */

/* "show jnx-example ..." */
static const parse_menu_t show_jnx_example_menu[] = {
    { "statistics", NULL, 0, NULL, jnx_exampled_show_statistics },
    { "data", "example data", 0, show_ex_data_menu, NULL },
    { NULL, NULL, 0, NULL, NULL }
};

/* "show ..." */
static const parse_menu_t show_menu[] = {
    { "jnx-example", NULL, 0, show_jnx_example_menu, NULL },
    { "version", NULL, 0, show_version_menu, NULL },
    { NULL, NULL, 0, NULL, NULL }
};

/* main menu */
const parse_menu_t master_menu[] = {
    { "show", NULL, 0, show_menu, NULL }, 
    { "quit", NULL, 0, NULL, jnx_exampled_quit },
    { NULL, NULL, 0, NULL, NULL }
};

These menus are quite self-explanatory. From the 'main menu', when users type 'show', it goes to the 'show menu'. 'version' and 'jnx-example' are the 2 valid choices under 'show' for jnx-exampled, and, 'statistics' and 'data' are the choices under 'show jnx-example'.

7.1.4. Preparing XML output

When the whole operational command 'show jnx-example statistics' is entered, traversing the menu structure ends up in calling function 'jnx_exampled_show_statistics'.

The goal of this function is to emit the output data inside the appropriate xml hierarchy and tags. The result is the xml output in section 7.1.2. above.

/*****************************************************************************/
/* Function : jnx_exampled_show_statistics                                   */
/* Purpose  : prints some system statistics like cpu/memory usage            */
/* Inputs   : msp - management socket pointer                                */
/* Return   : 0 if no error; 1 to terminate connection                       */
/*****************************************************************************/

static int
jnx_exampled_show_statistics (mgmt_sock_t *msp, parse_status_t *csb __unused,
                              char *unparsed __unused)
{
    XML_SUBTREE_OPEN2(msp, ODCI_JNX_EXAMPLE_STATISTICS, NULL);

    XML_ELT(msp, ODCI_CPU_USAGE, "%d", (int) stats_get_cpu_usage());
    XML_ELT(msp, ODCI_MEMORY_USAGE, "%d", (int) stats_get_memory_usage());

    XML_SUBTREE_CLOSE(ODCI_JNX_EXAMPLED_STATISTICS);
    return 0;
}

These functions have a basic structure,

  1. opens the node in the XML hierarchy
  2. emits the output data with the appropriate xml-tags
  3. closes the node in the XML hierarchy

where the xml-tags are defined in the corresponding odl file, jnx-example.odl

        /*                                                           */
        /* This odl file defines a DTD called junos-jnx-example.dtd  */
        /*                                                           */
        dtd junos-jnx-example;

        tag jnx-example-statistics {
            flag root;
            tag cpu-usage {
                help "CPU utilization (percent)";
                type int;
            }
            tag memory-usage {
                help "Memory Usage (KBytes)";
                type int;
            }

            format ....
        }

7.1.5. Formatting CLI display

With the xml output constructed, the last step is to construct the ODL format, so that the output data are arranged in a format that is legible for human users on the CLI.

(* Note that the easiest way to assist the development of the output ODL formats is to save the xml display in a file and use 'file render' on run-cli-bsd to construct the best format to display the data.)

...src/junos/lib/odl/input/jnx-example.odl

        tag jnx-example-statistics {
            flag root
            tag ....

            format jnx-example-statistics-heading-format {
                header "System Statistics:\\n";
                indent 2;

                line {
                    field cpu-usage template "CPU Usage: %d%%";
                }
                line {
                    field memory-usage template "Memory Usage: %d KB";
                }
            }
        }

7.2. 'show jnx-example user'

The configuration statement 'allowed-user' is added to the configuration schema for jnx-example. In this section, we are going to check whether the runtime user executing this new operational command 'show jnx-example user' has the same username as that configured in 'allowed-user username'.

7.2.1. DDL input

First the command 'show jnx-example user' has to be defined in the operational mode ddl input file, jnx-example.cmd.dd.

...src/junos/lib/ddl/input/jnx-example.cmd.dd

        #define DNAME_JNX_EXAMPLED "jnx-exampled"

        command juniper-command {

            /*                                                     */
            /* Example service commands                            */
            /*                                                     */
            command show {
                command jnx-example {
                    require jnx-example;
                    help "Show example service information";

                    command user {
                        help "Show users information";
                        action execute DNAME_JNX_EXAMPLED;
                    }
                }
            }
        }

Similar to 'show jnx-example statistics', mgd sends the whole operational command request to jnx-exampled for processing.

7.2.2. Proposed Output

The goal of the 'show jnx-example user' operational command is to check whether the runtime user executing this command has the same username as the one configured in the 'jnx-example allowed-user username' section.

So, there is only 1 username if the configured username and the username executing the command are the same; 2 usernames if the 2 are different.

Output display if the configured username is the same as the command user

       user@router> show jnx-example user | display xml
       <rpc-reply xmlns:junos="http://xml.juniper.net/junos/8.3/junos">
           <jnx-exampled-user>
               <eg-user-whoami>user</eg-user-whoami>
           </jnx-exampled-user>
           <cli>
               <banner></banner>
           </cli>
       </rpc-reply>

Output display if the configured username is different from the runtime user

       user@router> show jnx-example user | display xml
       <rpc-reply xmlns:junos="http://xml.juniper.net/junos/8.3/junos">
           <jnx-exampled-user>
               <eg-user-whoami>user</eg-user-whoami>
               <eg-user-configured>regress</eg-user-configured>
           </jnx-exampled-users>
           <cli>
               <banner></banner>
           </cli>
       </rpc-reply>

7.2.3. Adding 'show jnx-example user' to the operational command menu

The 'show jnx-example' menus already exist, we only need to add an entry 'user' to the existing structure, and add the function name to be called when 'show jnx-example user' is invoked.

...src/junos/usr.sbin/jnx-exampled/jnx-exampled_ui.c

        /* "show example ..." */
        static const parse_menu_t show_example_menu[] = {
            { "statistics", NULL, 0, NULL, jnx_exampled_show_statistics },
            { "data", "example data", 0, show_ex_data_menu, NULL },
            { "users", NULL, 0, NULL, jnx_exampled_show_user },
            { NULL, NULL, 0, NULL, NULL }
        };

The function 'show jnx-example user' executes is 'jnx_exampled_show_user'.

7.2.4. Preparing XML output

The goal is to prepare the xml tags to match the output displays shown in section 7.2.2. The tags are defined in the ODL input file.

        tag jnx-exampled-users-information {
            flag root;

            tag jnx-exampled-user {
                flag multiple;

                tag eg-user-configured {
                    help "Configured username";
                    type string;
                }
                tag eg-user-whoami {
                    help "Logged in username";
                    type string;
                }
            }
        }

In the ODL input file, all the possible xml-tags and the hierarchy of the tags are defined.

To emit the xml outputs in section 7.2.2., this is done with function jnx_exampled_show_user in jnx-example_ui.c.

...src/junos/usr.sbin/jnx-exampled/jnx-example_ui.c

/*****************************************************************************/
/* Function : exampled_show_user                                             */
/* Purpose  : prints user information                                        */
/* Inputs   : msp - management socket pointer                                */
/* Return   : 0 if no error; 1 to terminate connection                       */
/*****************************************************************************/
    
static int
jnx_exampled_show_user (mgmt_sock_t *msp, parse_status_t *csb __unused,
                    char *unparsed __unused)
{
    struct passwd *pw;
    const char *ex_style;

    pw = getpwuid(getuid());

    if (strcmp(ex_user.username, pw->pw_name)) { 
        ex_style = xml_attr_style(JNX_EXAMPLED_USER_INFORMATION_STYLE_MISMATCHED);
    } else {
        ex_style = xml_attr_style(JNX_EXAMPLED_USER_INFORMATION_STYLE_MATCHED);
    }

    XML_OPEN(msp, ODCI_EXAMPLED_USERS_INFORMATION, "%s %s",
             xml_attr_xmlns(XML_NS), ex_style);

    XML_SUBTREE_OPEN2(msp, ODCI_EXAMPLED_USERS, NULL);
    XML_ELT (msp, ODCI_EG_USER_WHOAMI, "%s", pw->pw_name);

    if (strcmp(e_user, pw->pw_name)) {
        XML_ELT (msp, ODCI_EG_USER_CONFIGURED, "%s", ex_user.username);
    }

    XML_SUBTREE_CLOSE(ODCI_EXAMPLED_USERS);

    XML_CLOSE(msp, ODCI_EXAMPLED_USERS_INFORMATION);

    return 0;
}

This function is very simple, it first extracts the username information from the system using getpwuid(getuid()). Then it compares this result with the configured username stored in ex_user.username. (Note that the configuration database is not read because all configuration settings should be read during commit time and stored in memory. Reading the configuration database is an expensive operation and it should only be done when necessary.)

After the runtime username is compared to the the configured username, it determines the xml outputs. If they match, only the runtime username is outputted; if they don't match, both the runtime and configured usernames are outputted. 2 stylesheets are used for the 2 different displays, jnx-exampled-user-information-style-matched is used for the former, and jnx-exampled-user-information-style-mismatched the latter.

7.2.5. Formatting CLI Display

The 2 stylesheets are defined in the ODL input file, jnx-exampled.odl.

        tag jnx-exampled-users-information {
            flag root;

            tag jnx-exampled-user{
                ....
            }

            style mismatched {
                format exampled-users-mismatched-format {
                    header "Login ID mismatched:\\n\\n";
                    indent 2;

                    line {
                        field eg-user-configured template "Configured User ID: %s";
                    }
                    line {
                        field eg-user-whoami template "Logged in ID: %s";
                    }
                }
            }
            style matched {
                format exampled-users-mismatched-format {
                    header "Login ID matched:\\n\\n";
                    indent 2;

                    line {
                        field eg-user-whoami template "Logged in ID: %s";
                    }
                }
            }
        }

The 2 stylesheets are similar. The 'matched' stylesheet only reports the runtime username, and the 'mismatched' stylesheets reports both the runtime username and the configured username.

The resulting output on the JUNOS device:

Matching runtime and configured jnx-example usernames:

        user@router> show example users 
        Login ID matched

          Logged in ID: user

        user@router>

Mismatched runtime and configured jnx-example usernames:

        user@router> show example users 
        Login ID mismatched

          Configured User ID: regress
          Logged in ID: user

        user@router>

2007-2009 Juniper Networks, Inc. All rights reserved. The information contained herein is confidential information of Juniper Networks, Inc., and may not be used, disclosed, distributed, modified, or copied without the prior written consent of Juniper Networks, Inc. in an express license. This information is subject to change by Juniper Networks, Inc. Juniper Networks, the Juniper Networks logo, and JUNOS are registered trademarks of Juniper Networks, Inc. in the United States and other countries. All other trademarks, service marks, registered trademarks, or registered service marks are the property of their respective owners.
Generated on Sun May 30 20:23:10 2010 for JUNOS UI Programmer's Guide by Doxygen 1.4.5