Solaris System Management Agent Developer's Guide

Chapter 3 Data Modeling

This chapter provides information on how to modify the init_module() routine of a module to handle various types of data. The chapter discusses the related code examples that are provided with the System Management Agent:

demo_module_1

Scalar data example

demo_module_2

Simple table example

demo_module_3

General table example

The chapter includes the following topics:

init_module Routine

When a module is loaded in the agent, the agent calls the init_module() routine for the module. The init_module() routine registers the OIDs for the objects that the module handles. After this registration occurs, the agent associates the module name with the registered OIDs. All modules must have this init_module() routine.

The mib2c utility creates the init_module() routine for you. The routine provides the basic code for data retrieval, which you must modify appropriately for the type of data.

If you have several MIB nodes in your MIB, the mib2c utility creates several .c files. Each generated file contains an init_mibnode() routine. A module must have only one initialization routine, which must conform to the convention of init_module(). Therefore, when you have more than one MIB node represented in your module, you must combine the initialization content of all the generated .c files into one file to ensure that the initialization routine for each MIB node is called by init_module().

You can combine files to build a module in one of the following ways:

In both cases, the rest of the code in myMib.c might be similar to the following pseudo code:

/* get/set handlers for scalarGroup found in scalarGroup.c */

/* get_first/get_next/handler for tableGroup - found in tableGroup.c */
 

The following sections discuss how the data retrieval code must be modified in your module for different types of data.

Scalar Objects

Scalar objects are used for singular variables that are not part of a table or an array. If your MIB contains scalar objects, you must run mib2c with a scalar-specific configuration file on the MIB nodes that contain the scalars. You should use the following command, where mibnode1 and mibnode2 are top-level nodes of scalar data for which you want to generate code:


mib2c -c mib2c.scalar.conf mibnode1 mibnode2 ...

You can specify as many nodes of scalar data as you want. This command generates two C code files that are named mibnode.c and mibnode.h for each MIB node that is specified in the command line. You must modify the mibnode1.c and mibnode2.c files to enable the agent to retrieve data from scalar objects. See the mib2c(1M) man page for more information about using the mib2c tool.

Now, compile the MIB and example code as described in demo_module_1 Code Example for Scalar Objects.

demo_module_1 Code Example for Scalar Objects

The demo_module_1 code example is provided to help you understand how to modify the code generated by the mib2c command to perform a scalar data retrieval. The demo_module_1 code example is located by default in the directory /usr/demo/sma_snmp/demo_module_1.

The README_demo_module_1 file contains instructions that describe how to perform the following tasks:

The demo_module_1 is set up to allow you to generate code templates me1LoadGroup.c and me1LoadGroup.h. You can then compare the generated files to the files demo_module_1.c and demo_module_1.h. The mib2c utility generates me1LoadGroup.c, which contains the init_me1LoadGroup() function. You should compare this function to the init_demo_module_1() function in the demo_module_1.c file.

The demo_module_1.c and demo_module_1.h files have been modified appropriately to retrieve scalar data. You can use these files as a model for learning how to work with scalar data in your own module. The instructions then explain how to compile the modified source files to create a functioning module.

Modifications for Scalar Data Retrieval

The demo_module_1 example code, demo_module_1.c, provides the system load average for 1, 5 and 15 minutes, respectively.

The init_demo_module_1() function call defines the OIDs for the following three scalar objects:

These OIDs are set up in the demo_module_1.c source file, to reflect what is in the SDK-DEMO1-MIB.txt. The OIDs are defined as follows:


static oid me1SystemLoadAvg15min_oid[] = 
  { 1,3,6,1,4,1,42,2,2,4,4,1,1,3, 0 };
static oid me1SystemLoadAvg1min_oid[] = 
  { 1,3,6,1,4,1,42,2,2,4,4,1,1,1, 0 };
static oid me1SystemLoadAvg5min_oid[] = 
  { 1,3,6,1,4,1,42,2,2,4,4,1,1,2, 0};

The mib2c command used the netsnmp_register_read_only_instance() function to register these handler functions:

In this way, when a GET or GET_NEXT request is received, the corresponding handler function is called.

For example, for the 15 minute load average, you can manually register the get_me1SystemLoadAvg15min() handler function. The handler retrieves data on the me1SystemLoadAvg15min scalar. You must place the handler in the netsnmp_register_read_only_instance() function as follows:


netsnmp_register_read_only_instance
(netsnmp_create_handler_registration
("me1SystemLoadAvg15min",
get_me1SystemLoadAvg15min,
me1SystemLoadAvg15min_oid,
OID_LENGTH(me1SystemLoadAvg15min_oid),
HANDLER_CAN_RONLY));

Alternatively, you can use the mib2c command to generate the function bodies of the handler functions for you. Replace /* XXX... in the generated code with your own data structure for returning the data to the requests. For instance, the following code must be modified:

case MODE_GET:
snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR, (u_char
 *) /* XXX: a pointer to the scalar's data */,
 /* XXX: the length of the data in bytes */);
break;

This code must be modified to include your own data structure for returning data to the requests. Replace the /* XXX... that is shown in the preceding code.

case MODE_GET:
data = getLoadAvg(LOADAVG_1MIN);
snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR, (u_char
 *) data , strlen(data));
free(data);
break;

Note that the input MIB file contains the specification of a table as well as scalar data. When you run mib2c -c mib2c.scalar.conf scalar-node the template code is generated only for the scalar nodes in the MIB.

Simple Tables

A simple table has four characteristics:

If any of these conditions are not met, the table is not a simple table but a general table. The techniques described here are applicable only to simple tables.


Note –

mib2c assumes that all tables are simple. For information on handling the general tables case, see General Tables.


If your MIB contains simple tables, you must run mib2c with a configuration file that handles code generation for simple tables. You should use the following command, where mibnode1 and mibnode2 are top level nodes of tabular data for which you want to generate code:


mib2c -c mib2c.iterate.conf mibnode1 mibnode2 ...

You can specify as many nodes of simple table data as you want. This command generates two C code files that are named mibnode.c and mibnode.h for each MIB node that is specified in the command line. You must modify the mibnode1.c and mibnode2.c files to enable the agent to retrieve data from simple tables. See the mib2c(1M) man page for more information about using the mib2c tool.

The demo_module_2 code example shows how to generate code templates for simple tables.

demo_module_2 Code Example for Simple Tables

The demo_module_2 code example is provided to help you understand how to modify the code generated by the mib2c command to perform data retrieval from simple tables. The demo_module_2 code example is located by default in the directory /usr/demo/sma_snmp/demo_module_2.

The README_demo_module_2 file contains instructions that describe how to do the following tasks:

The demo_module_2 is set up to allow you to generate code templates me1FileTable.c and me1FileTable.h. You can then compare the generated files to the files demo_module_2.c and demo_module_2.h.

The mib2c utility generates me1FileTable.c, which contains the init_me1FileTable() function. You should compare this function to the init_demo_module_2() function in the demo_module_2.c file.

Modifications for Simple Table Data Retrieval

In demo_module_2.c, the init_demo_module_2 routine calls the initialize_table_me1FileTable() function. The initialize_table_me1FileTable() function registers the OID for the table handled by the function. The function also calls some Net-SNMP functions to initialize the tables.

You should provide the table data in this initialize_table_me1FileTable() function if needed. The initialize_table_me1FileTable() function performs the following:

Initialization

The initialize_table_me1FileTable() function performs the real table initialization, by performing tasks such as setting the maximum number of rows and columns.

OID Table Definition

The initialize_table_me1FileTable() function defines the table OID:

static oid me1FileTable_oid[] =
  {1,3,6,1,4,1,42,2,2,4,4,1,2,1};
Table Definition

The initialize_table_me1FileTable() function sets up the table's definition. This function specifies another function to call, me1FileTable_get_first_data_point(), to process the first row of data in the table. The function me1FileTable_get_next_data_point() is called to process the remaining rows in the table.

netsnmp_table_helper_add_indexes(table_info,
ASN_UNSIGNED, /* index: me1FileIndex */
0);

     table_info->min_column = 1;
     table_info->max_column = 4;

  /* iterator access routines */
iinfo->get_first_data_point = 
           me1FileTable_get_first_data_point;
iinfo->get_next_data_point = 
           me1FileTable_get_next_data_point;
iinfo->table_reginfo = 
           table_info;

iinfo is a pointer to a netsnmp_iterator_info structure.

Master Agent Registration

The initialize_table_me1FileTable() function registers the table with the master agent:

netsnmp_register_table_iterator(my_handler, iinfo);

The table iterator is a helper function that modules can use to index through rows in a table. Functionally, the table iterator is a specialized version of the more generic table helper. The table iterator eases the burden of GETNEXT processing. The table iterator loops through all the data indexes retrieved through those function calls that should be supplied by the module that requests help. See the API documentation at /usr/sfw/doc/sma_snmp/html/group__table__iterator.html for more information on table iterator APIs.

Note that the input MIB file contains the specification of table and scalar data. However, when you run mib2c with mib2c.iterate.conf and specify the table node name, only template code for the simple table in the MIB is generated.

Data Retrieval From Large Simple Tables

Data retrieval from a simple table requires you to use the single, integer index subidentifier to index into an existing data structure.

With some modules, this underlying table might be relatively large, or only accessible through a cumbersome interface. Data retrieval might be very slow, particularly if performing a walk of a MIB tree requires the table to be reloaded for each variable requested. In these circumstances, a useful technique is to cache the table on the first read and use that cache for subsequent requests.

To cache the table, you must have a separate routine to read in the table. The routine uses two static variables. One variable is a structure or array for the data. The other variable is an additional timestamp to indicate when the table was last loaded. When a call is made to the routine to read the table, the routine can first determine whether the cached table is sufficiently new. If the data is recent enough, the routine can return immediately. The system then uses the cached data. If the cached version is old enough to be considered out of date, the routine can retrieve the current data. The routine updates the cached version of the data and the timestamp value. This approach is particularly useful if the data is relatively static, such as a list of mounted file systems.

Multiple SET Processing in demo_module_2

The demo_module_2 example code shows how to perform a multiple OID set action. In this case, a file name and row status are provided.

When the agent processes a SET request, a series of calls to the MIB module code are made. These calls ensure that all SET requests in the incoming packet can be processed successfully. This processing allows modules the chance to get out of the transaction sequence early. If the module gets out of one transaction early, none of the transactions in the set are completed, in order to maintain continuity. However, this behavior makes the code for processing SET requests more complex. The following diagram is a simple state diagram that shows each step of the master agent's SET processing.

Figure 3–1 Set Processing State Diagram

State diagram shows each
step of the master agent's set processing.

An operation with no failures is illustrated by the vertical path on the left, in the preceding figure. If any of the MIB modules that are being acted upon returns an error, the agent branches to one of the failure states. The failure states are on the right side in the figure. These failure states require you to clean up and, where necessary, undo the actions of previous steps in your module.

See the me1FileTable_handler() function in the demo_module_2 example code, for how to perform SET requests in different states. The following is list describes each of the states:

case MODE_SET_RESERVE1

Checks that the value being set is acceptable.

case MODE_SET_RESERVE2

Allocates any necessary resources. For example, calls to the malloc() function occur here.

case MODE_SET_FREE

Frees resources when one of the other values being SET failed for some reason.

case MODE_SET_ACTION

Sets the variable as requested and saves information that might be needed in order to reverse this SET later.

case MODE_SET_COMMIT

Operation is successful. Discards saved information and makes the change permanent. For example, writes to the snmpd.conf configuration file and frees any allocated resources.

case MODE_SET_UNDO

A failure occurred, so resets the variable to its previous value. Frees any allocated resources.

You can perform the set action using either of the following commands when you use the demo_module_2 example:


snmpset -v1 -c private localhost me1FileTable.1.2.3 s "test"

snmpset -v1 -c private localhost .1.3.6.1.4.1.42.2.2.4.4.1.2.1.1.2.2 s "test"

These commands change the file that you want to monitor.


Note –

In order to use the snmpset command to specify a different file name, you must have a private community string in the snmpd.conf file, which is located in /etc/sma/snmp or $HOME/.snmp.


General Tables

A general table differs from a simple table in at least one of the following ways:

The command that you use to generate code templates for general tables is the same command used for simple tables:


mib2c -c mib2c.iterate.conf mibnode1 mibnode2 ...

The demo_module_3 code example shows how modify the templates appropriately to retrieve data from general tables.

demo_module_3 Code Example for General Tables

The demo_module_3 code example is provided to help you understand how to modify the code generated by the mib2c command to perform a data retrieval in a general table. The table example provides information for monitoring a list of files. The demo_module_3 code example is located by default in the directory /usr/demo/sma_snmp/demo_module_3.

The README_demo_module_3 file contains instructions that describe how to perform the following tasks:

The demo_module_3 is set up to allow you to generate code templates me1ContactInfoTable.c and me1ContactInfoTable.h. You can then compare the generated files to the files demo_module_3.c and demo_module_3.h.

The me1ContactInfoTable.c and me1ContactInfoTable.h have been modified appropriately to retrieve data from general tables. You can use these files as a model for learning how to work with general tables in your own module. The instructions then explain how to compile the modified source files to create a functioning module.

The demo_module_3 code was generated by using mib2c with the -c mib2c.iterate.conf option. Some functions have been added to implement a link list to provide the test data.

The example uses some dummy data to perform data retrieval for a two-index table. The code is similar to the demo_module_2.c with one extra index. The following code sets up the table with two indexes:

netsnmp_table_helper_add_indexes(table_info,
ASN_INTEGER, /* index: me1FloorNumber */
ASN_INTEGER, /* index: me1RoomNumber */
0);

Use care in returning the “NEXT” data when function me1ContactInfoTable_get_next_data_point() is called. For instance, the data in this table is presorted so the next data is conveniently pointed by the pNext pointer in this example code:

me1ContactEntry* nextNode = (me1ContactEntry*) *my_loop_context;
          nextNode = nextNode->pNext;

If your implementation is more complicated, make sure the OIDs are increased incrementally, (xxx.1.1, xxx.1.2, ).

The input MIB file contains the specification of tables and scalars. When you run mib2c -c mib2c.iterate.conf on a general table node, template code is generated only for the general table in the MIB.