[Top] [Prev] [Next] [Bottom]


14 - Converting an Existing Application to an Agent

This chapter shows an example of quick agent development using an existing application. The example introduces a simple intermediate programming layer between a generic agent services interface and the application. Thus, converting the application to an agent becomes an almost mechanical process.

The examples assume that Site/SunNet/Domain Manager has been installed on a system being used to develop agents. All files cited are presumed to be in the sample agent directory:

( Chapter 13, "Testing and Integration ," has details on how to compile your agent and install it into the network.)

The example uses the following staged approach:


1. Write and test a standalone application that gets the information desired.


2. Organize the information into groups of attributes and write the agent schema file.


3. Rewrite and test the application with a reporting interface not including agent services.


4. Build an agent by including agent services and testing with snm_cmd.


5. Test the agent with the Site/SunNet/Domain Manager Console, snm.
In this chapter, you will write an agent by modifying an existing sample agent. The output shown herein is based on the sample agent. Yours will be different, specific to your application. Also, note this chapter does not discuss agent initialization, startup, or shutdown.


14.1 Write and Test the Standalone Program

Start with a standalone application. The example application reports system information, for example, machine type, and the date.

The following listing shows the source code for the kernel memory buffer application.


 /* application.c */ 
#include <sys/systeminfo.h>
#include <time.h> 
extern void send_error(); 
static char *day_of_week[7] = {
 	"Sunday",
 	"Monday",
 	"Tuesday", 
	"Wednesday",
 	"Thursday",
 	"Friday",
 	"Saturday" 
}; 
static char *month_of_year[12] = {
  	"January",
 	"February",
 	"March", 
	"April", 
	"May",
 	"June",
 	"July",
 	"August",
 	"September",
 	"October",
 	"November",
 	"December" }; 
#ifndef	AGENT 
/*
 




------------------------------------------------------------------------------------------
*  main (standalone application) 
------------------------------------------------------------------------------------------
*/ 
main(argc, argv)
     int argc;
     char **argv; { 
#else
#include <netmgt/netmgt.h> 
/*
-----------------------------------------------------------------------------------------
*  sample_data - sample performance data and build report
*	returns TRUE if successful; otherwise returns FALSE
----------------------------------------------------------------------------------------
*/
bool_t
sample_data(system, group, key)
    char *system;		/* system name */
    char *group;		/* group name */ 
    char *key;			/* table key name */
{
#endif 
#define SYSNAME_LEN 25 
#define RELEASE_LEN 7 
#define MACHINE_LEN 7 
#define SERIALNUMBER_LEN 50 
    char message[64];		/* error message buffer */ 
	char sysname[SYSNAME_LEN];		/* OS name */
 	char release[RELEASE_LEN];		/* OS release */
 	char machine[MACHINE_LEN];		/* machine type (eg. sun4c) */
 	char serialnumber[SERIALNUMBER_LEN];	/* machine's built in serial # */ 
	char *weekday; 
	char *month;
 	short	day;
 	short	year; 
 




	struct tm 	*time_info; 
	time_t		time_val; 
	char *group;
 	if (argc != 2) {
 		printf("usage:  na.sample group\n");
 		exit(0); 
	}
 	group = argv[1]; 
   if (strcmp(group, "sysinfo") == 0) {
 		sysinfo(SI_SYSNAME, sysname, SYSNAME_LEN);
 		sysinfo(SI_RELEASE, release, RELEASE_LEN);
 		sysinfo(SI_MACHINE, machine, MACHINE_LEN);
 		sysinfo(SI_HW_SERIAL, serialnumber, SERIALNUMBER_LEN); 
    	/* print sysinfo results */    
 	(void) printf("sysname = %s\n", sysname);  
   	(void) printf("release = %s\n", release);
     	(void) printf("machine = %s\n", machine);
     	(void) printf("serialnumber = %s\n", serialnumber); 
} 
    if (strcmp(group, "date") == 0) {
  		time_val = time((time_t *)NULL);
 		time_info = localtime(&time_val); 
		/* convert values */
 		weekday = day_of_week[time_info->tm_wday];
 		month = month_of_-year[time_info->tm_mon];
 		day = time_info->tm_mday;
 		year = time_info->tm_year + 1900; 
		/* print out date report */
 		(void) printf("weekday = %s\n", weekday );
 		(void) printf("month = %s\n", month);
 		(void) printf("day = %d\n", day);
 		(void) printf("year = %d\n", year); 
    } 
 




#ifndef AGENT 
    exit(0);
#else
     /* indicate success */
     return TRUE;
#endif
} 
 

Use the Makefile in the sample agent directory to build the application. Run the application to get a feel for the information it collects. (Note the values reported in these examples will probably differ from those on your system.) Enter the command:


host% cd <sample-agent-path> 
 

where <sample-agent-path > is /opt/SUNWconn/snm/src/sample for a Solaris 2.x installation or /usr/snm/src/sample for the Solaris 1.x version.




host% make application
 

Now enter:

This command creates an executable application in the file Application/na.sample. Run the application:


host% Application/na.sample sysinfo
sysname = SunOS
release = 5.1
machine = sun4m
serialnumber = 1918901280
host% Application/na.sample date
weekday = Friday
month = March
day = 12
year = 1993
host% 
 
 


14.2 Organize the Information and Write a Schema File

The information provided falls into two groups: a summary of memory buffer statistics and a detailed report of statistics. From this are defined the two groups sysinfo and date.

Within these groups attributes are defined, one for each piece of information reported. Note data types of the data and assign types from Table 11-1 on page 11-3 . Name each data field (using no more than 64 characters) and write a short description of each field (using no more than 1024 characters). Note any error messages in the standalone application code.

The following is the agent schema file, sample.schema, written for this example.




#
# @(#)sample.schema 2.7 93/02/01 SMI
#
# sample.schema - SunNet Manager sample statistics agent schema definition
#
agent sample
    description "sample system info agent"
    rpcid 100113
    serial 1
(
    group sysinfo
    description "system info"
    (
	string[20]sysname	description "operating system name"
	string[5] release	description "operating system release"
	string[5] machine	description "machine name (eg. sun4, sun4c, sun4m)"
	string[50]	serialnumber	description "ascii representation of serial number"
    )
    group date
    description "date according to system"
    (
	string[10] weekday		description "day of the week"
	string[10] month	description "current month "	
	short        day				description "current day of month [number 1 - 31]"
	short        year			description "year"
    )
    agenterrors
    (
	1	"Can't report summary group"
	2	"Can't report detail group"
    )
)
 
 


14.3 isGroup() Function

After defining the schema, you should write a function to validate group names. The validation routine will be used by the request verification routine, verify_request(), in the file, request.c . In the example, the group names are placed into the groupnames array in the file schema.c as shown below. (The #ifdef 'ed code in the file is used during the testing described in Section 14.4, "Rewrite with the Reporting Interface," on page 14-11 .)




/* schema.c */
#include <netmgt/netmgt.h>
/* ---------------------------------------------------------------------------------------
 *  groupnames - group names defined in the agent schema
 * ---------------------------------------------------------------------------------------
 */
static char *groupnames[] = {
    "sysinfo",
    "date",
    0
};
/* ---------------------------------------------------------------------------------------
 *  isGroup - determines if 'groupname' is a group
 *	returns TRUE if a group; otherwise returns FALSE
 * ---------------------------------------------------------------------------------------
 */
bool_t
isGroup(groupname)
    char *groupname;
{
    register char **p;		/* utility pointer */
    for (p = groupnames; *p; p++)
	if (strcmp(groupname, *p) == 0)
	    return TRUE;
    return FALSE;
}
#ifdef NOAGENTSERVICES
/* ---------------------------------------------------------------------------------------
 *  main - main routine to debug the report building routines.  This section of code is
 *	used to test the application with the agentinterface but without using the agent services 
 * ---------------------------------------------------------------------------------------
 */
	 
 




main()
{
    register char **p;		/* utility pointer */
    char *system = "<local host>";	/* system name */
    char *key = "<no key>";	/* table key */
    /* report all agent groups */
    for (p = groupnames; *p; p++) {
	(void) printf("system = \"%s\", group = \"%s\", key = \"%s\"\n",system, *p, key);
	sample_data(system, *p, key);
    }
    exit(0);
}
#endif
 


14.4 Rewrite with the Reporting Interface

The reporting interface is a simplified programming interface to agent services. The idea is to isolate the application and agent services so the application can be transformed into an agent independent of the way those services are used. In this way, you are "bringing agent services to the application" rather than vice versa.

The reporting interface consists of the files: start.c, request.c, and interface.c . These files are in the sample agent directory. Each file is responsible for a specific function as follows:

start.c

contains the agent main routine and executes agent initialization and startup.
request.c
contains routines that execute request verification and dispatching. The dispatch function contains the reporting algorithm used by the agent. In the example, the agent attributes are assumed to be instantaneously sampled and reported periodically, on the interval specified in the request.
interface.c
contains a wrapper to the agent service routines that build message lists for event reports, data reports, and error reports. The wrapper reduces these calls to routines that sample attributes by data type. Calls to the wrapper routines can be embedded in the application at the point where attributes are sampled. This file also contains #ifdef'ed code for testing with the sample interface but without agent services.
These files contain the interfaces to agent services. The last file is the intermediate layer that ties the agent service interface to the application. In this section, you will add the intermediate layer to the application. In the next section, you will add the agent services interface.

The intermediate layer consists of the following wrapper routines in interface.c:

send_error()

sends back errors detected by the standalone application code. The arguments consist of a system error code, an optional agent error code for agent defined errors and an optional message string which describes the specific error.
get_option_string()
retrieves the options string passed in the agent request. The options string is set in the Options field of the Console's data report and event report windows.
fe()
is a set of routines for building data buffers by data type. These routines call build_report() which builds the data or event report.


/* interface.c */ 
#include <netmgt/netmgt.h> 
#ifndef NOAGENTSERVICES 
/* --------------------------------------------------------------------- 
 * send_error - send an error report 
* no return value

* ---------------------------------------------------------------------

*/

void

send_error(service_error, agent_error, message)

Netmgt_stat service_error; /* service error code */

u_int agent_error; /* agent error code defined in agent schema */

char *message; /* error message string */

{ 
Netmgt_error error; /* error report buffer */

error.service_error = service_error;

error.agent_error = agent_error;

error.message = message;

if (!netmgt_send_error(&error))

NETMGT_DBG("na.sample: can't send error report: %s\n",

    netmgt_sperror());

return;

} 
#else 
void 
send_error(system_error, agent_error, message) 
Netmgt_stat system_error; /* system error code */

u_int agent_error; /* agent error code */

char *message; /* error message */

{ 
printf("system_error = %d, agent_error = %d, message = \"%s\"\n",

system_error, agent_error, message);

return;

} 
#endif 
 





#ifndef NOAGENTSERVICES 
/* ---------------------------------------------------------------------

* get_option_string - retrieve the options string from the agent

* request. The options string is set in the Options field of

* the Console's data and event request windows. This

* function returns the option string if there is one; otherwise

* it returns (char *)NULL.

* ---------------------------------------------------------------------

 */ 
char * 
get_option_string() 
{ 
Netmgt_arg arg; /* request argument buffer */

if (!netmgt_fetch_argument(NETMGT_OPTSTRING, &arg))

return (char *) NULL;

return (char *) arg.value;

} 
#else 
char * 
get_option_string() 
{ 
return ("<option string>");

} 
#endif 
#ifndef NOAGENTSERVICES 
/* -------------------------------------------------------------------- 
* build_report - build a data or event report from data buffer

* returns TRUE if successful; otherwise returns FALSE

* --------------------------------------------------------------------

 */ 
bool_t 
build_report(data) 
Netmgt_data *data; /* data buffer */

{

bool_t event; /* whether an event occurred */

Netmgt_error error; /* error buffer */

 






/* build the report */

if (!netmgt_build_report(data, &event)) {

/* fetch error buffer */

if (!netmgt_fetch_error(&error)) {

NETMGT_DBG("na.event: can't fetch error: %s\n",

netmgt_sperror());

return FALSE;

}

/* send the error report to the rendezvous */

if (!netmgt_send_error(&error)) {

NETMGT_DBG("na.event: can't send error report: %s\n",

netmgt_sperror());

return FALSE;

}

return FALSE;

}

/* indicate that an event occurred if tracing */

if (event)

NETMGT_DBG("*** na.sample: event occurred ***\n");

return TRUE;

} 
#else 
bool_t 
build_report(data) 
Netmgt_data *data; /* data buffer */

{ 
printf("<build report>\n");

} 
#endif 
#ifndef NOAGENTSERVICES 
/* ---------------------------------------------------------------------

* build_<data type>

* The following are a set of routines for building data and

* event reports by data type. These routines return TRUE if

* successful; otherwise they return FALSE.

* ---------------------------------------------------------------------

*/

 






bool_t 
build_short(name, value) 
char *name;

short value;

{ 
Netmgt_data data; /* data buffer */

(void) strcpy(data.name, name);

data.type = NETMGT_SHORT;

data.length = sizeof(short);

data.value = (caddr_t) & value;

return build_report(&data);

} 
... 
#else 
/* --------------------------------------------------------------------- 
 * This section of code is used for testing the application with 
 * the reporting interface but without using agent services. 
 * --------------------------------------------------------------------- 
 */ 
bool_t 
build_short(name, value) 
char *name;

short value;

{

printf("%s: (short) %d\n", name, value);

return TRUE;

} 
... 
#endif 
 


14.4.1 Modify the Application

To rewrite the application, first include <netmgt/netmgt.h> . Next, rewrite the main routine to be callable from the dispatch_request() routine with the parameters system, group, and key. The three parameters are character strings indicating the system for which information is desired, the group name of the attribute group desired, and the key selector to be used if the group name specifies a table. For tables, the attribute netmgt_table_key should also be returned. This is useful for setting attribute values with the Console's Set tool or for graphing attributes from the Results Browser tool.

Error messages should be converted to calls to send_error() with the agent error code for the appropriate message as defined by the agent schema.

Reporting is carried out by group name. Calls to the build_<data_type>() routines should be bracketed by an if statement that checks for the appropriate group name.

When a report is built, the returned (boolean) value of the call should be checked. The rewritten application should return the boolean value FALSE if any of the build_<data_type>() calls failed. Otherwise it should return TRUE.

Reporting is done by using the appropriate build_<data_type>() routine for the type of information to be collected. This involves replacing printf() calls with build_<data_type>() calls. The reporting routines transcribe the information into an event or data report according to the algorithm in the file request.c.

The rewritten application is shown below. Note the modified code sections are indicated using #ifdefAGENT statements. The application can be re-compiled with the -DAGENT compiler switch set to build an agent. It can be compiled without it to obtain the original application.




/* application.c */
#include <sys/systeminfo.h>
#include <time.h>
extern void send_error();
static char *day_of_week[7] = {
	"Sunday",
	"Monday",
	"Tuesday",
	"Wednesday",
	"Thursday",
	"Friday",
	"Saturday"
};
static char *month_of_year[12] = {
	"January",
	"February",
	"March",
	"April",
	"May",
	"June",
	"July",
	"August",
	"September",
	"October",
	"November",
	"December"
};
#ifndef	AGENT
/* -----------------------------------------------------------------------
 *  main (standalone application)
 * -----------------------------------------------------------------------
 */
 




*/
main(argc, argv)
    int argc;
    char **argv;
{
#else
#include <netmgt/netmgt.h>
/* -----------------------------------------------------------------------
 *  sample_data - sample performance data and build report
 *	returns TRUE if successful; otherwise returns FALSE
 * -----------------------------------------------------------------------
 */
bool_t
sample_data(system, group, key)
    char *system;		/* system name */
    char *group;		/* group name */
    char *key;			/* table key name */
{
#endif
#define SYSNAME_LEN 25
#define RELEASE_LEN 7
#define MACHINE_LEN 7
#define SERIALNUMBER_LEN 50
    char message[64];		/* error message buffer */
	char sysname[SYSNAME_LEN];		/* OS name */
	char release[RELEASE_LEN];		/* OS release */
	char machine[MACHINE_LEN];		/* machine type (eg. sun4c) */
	char serialnumber[SERIALNUMBER_LEN];	/* machine's built in serial # */
	char *weekday;
	char *month;
	short	day;
	short	year;
	struct tm	*time_info;
	time_t		time_val;

 







#ifndef AGENT
	char *group;
	if (argc != 2) {
		printf("usage:  na.sample2 group\n");
		exit(0);
	}
	group = argv[1];
#endif
   }
   if (strcmp(group, "sysinfo") == 0) { 
		sysinfo(SI_SYSNAME, sysname, SYSNAME_LEN);
		sysinfo(SI_RELEASE, release, RELEASE_LEN);
		sysinfo(SI_MACHINE, machine, MACHINE_LEN);
		sysinfo(SI_HW_SERIAL, serialnumber, SERIALNUMBER_LEN);
#ifndef AGENT
    	/* print sysinfo results */
    	(void) printf("sysname = %s\n", sysname);
    	(void) printf("release = %s\n", release);
    	(void) printf("machine = %s\n", machine);
    	(void) printf("serialnumber = %s\n", serialnumber);
#else
	/* build sysinfo report */
		if (!build_string("sysname", sysname) ||
			!build_string("release", release) ||
			!build_string("machine", machine) ||
	    	!build_string("serialnumber", serialnumber)) {
	    	/*
 	* fatal error - send an error report to the rendezvous and return
	     	*/
	send_error(NETMGT_FATAL, (u_int) 1, (char *) NULL);
  		return FALSE;
		}
#endif
    }
    if (strcmp(group, "date") == 0) { 
          time_val = time((time_t *)NULL);
		time_info = localtime(&time_val);
 





 
		/* convert values */
		weekday = day_of_week[time_info->tm_wday];
		month = month_of_year[time_info->tm_mon];
		day = time_info->tm_mday;
		year = time_info->tm_year + 1900;
#ifndef AGENT
		/* print out date report */
		(void) printf("weekday = %s\n", weekday );
		(void) printf("month = %s\n", month);
		(void) printf("day = %d\n", day);
		(void) printf("year = %d\n", year);
#else
	/* build date report */
		if (!build_string("weekday", weekday) ||
			!build_string("month", month) ||
			!build_short("day", day) ||
  	!build_short("year", year)) {
  	/*
   	* fatal error - send an error report to the rendezvous and return
	         	*/
   		send_error(NETMGT_FATAL, (u_int) 1, (char *) NULL);
  		return FALSE;
		}
#endif
    }
#ifndef AGENT
    exit(0);
#else
    /* indicate success */
    return TRUE;
#endif

 


14.4.2 Build with Report Interface

Having made these changes to the application, build a test version that omits agent services using the following commands:


host% cd  <sample-agent-path>
 

where <sample-agent-path > is /opt/SUNWconn/snm/src/sample if this is a Solaris 2.x installation or /usr/snm/src/sample if you have installed the Solaris 1.1 version of this product.

Then enter the following command:


host% make test 
 

This command creates a test application executable in the file Test/na.sample.

14.4.3 Test with Report Interface

Run the test application and verify the group names and that all appropriate attributes are sampled. It is an error to return an attribute not specified for a group as defined by the agent schema.

The problem is that only root can access kernel memory structures. Become root and try again:


host% Test/na.sample
system = "<local host>", group = "sysinfo", key = "<no key>"
sysname: "SunOS"'
release: "5.1"'
machine: "sun4m"'
serialnumber: "1918901280"'
system = "<local host>", group = "date", key = "<no key>"
weekday: "Friday"'
month: "March"'
day: (short) 12
year: (short) 1993
host%
 


14.5 Build the Agent and Test with snm_cmd

Build the agent with the agent services library. In the sample code directory enter the command:


host% make agent 
 

This command creates an executable agent in the file Agent/na.sample. Testing the agent with snm_cmd. snm_cmd is described in greater detail in Section 13.2, "Test the Agent," on page 13-7 . Ensure the logger is running. If it isn't running, start it with the following command,


host% <agents-path>/na.logger 
 

where <agents-path > is /opt/SUNWconn/snm/agents if this is a Solaris 2.x installation or /usr/snm/agents if this is the SunOS 4.x version.

Ensure the sample agent service is registered. (See Section 13.1.10, "Register the Agent RPC Program Number," on page 13-6 .) The mechanism for starting the agent must be determined. The agent can be started either automatically by inetd or manually from the command line. Manually starting an agent has the advantage that messages from the agent will be reported and any command line debug switches can be used. These are unavailable when using inetd. When using inetd to start the agent, verify that an entry for the sample agent exists in /etc/inetd.conf, and it refers to the proper file to execute.

Assuming that a Solaris 2.4 version of Site/SunNet/Domain Manager is installed in /opt/SUNWconn/snm , here is what the sample agent entry should look like.


sample/10 TLI rpc/udp wait root /opt/SUNWconn/snm/src/sample/Agent/na.sample na.sample 
 

Assuming the Solaris 1.1.1 version of this product installed in /usr/snm, here is what the sample agent entry should look like.


sample/10 TLI rpc/udp wait root /usr/snm/src/sample/Agent/na.sample na.sample 
 

If the inetd.conf file must be modified, remember to send a SIGHUP signal to the inetd process so that the change will be noted.

Once the mechanism for starting the agent has been established, it can be tested with snm_cmd . Test the agent by making data requests for all groups. The following is an example with the Solaris 2.4 version of this product:


host% /opt/SUNWconn/snm/bin/snm_cmd -d -a sample -g sysinfo -s
success: timestamp = 629774235.520002
sysname: SunOS
release: 5.1
machine: sun4m
serialnumber: 1918901280
host% /opt/SUNWconn/snm/bin/snm_cmd -d -a sample -g date -s
success: timestamp = 629774257.220002
weekday: Friday
month: March
day: (short) 12
year: (short) 1993
host%
 

The next example illustrates this use of snm_cmd in a Solaris 1.1 environment:


host% /usr/snm/bin/snm_cmd -d -a sample -g sysinfo -s
success: timestamp = 629774235.520002
sysname: SunOS
release: 4.1.3
machine: sun4m
serialnumber: 1918901280
host% /usr/snm/bin/snm_cmd -d -a sample -g date -s
success: timestamp = 629774257.220002
weekday: Friday
month: December
day: (short) 17
year: (short) 1993
host%
 


14.6 Test the Agent with the Console

Test the agent with snm . Ensure the new agent schema file is specified either by being moved into a directory listed by the schemas keyword in snm.conf or by being included on the snm command line.

The system where the test will be made should have its management database (MDB) record cluster modified to include a reference to the sample agent. For more information on the MDB and how to specify element representations, see the Administration Guide. The agent will be tested on the same system on which it was built. Following is an example record cluster for the test system used in this example:


cluster( 
    component.sun3 ( prospero 128.10.2.26 "Earvin Johnson" ) 
    membership ( Home ) 
    glyphColor ( 208 0 255 ) 
    agent ( sample ) 
)
 


1. Run snm and bring up the Data Log window.


2. Bring up the glyph menu of the test system and do a Quick Dump of all the groups under the sample agent.


3. Try to send some event reports.
This agent application collects static data so it is easy to set an event on any particular value. Agents with rapidly changing data can often be induced to send a report by setting an attribute threshold to a value the attribute does not normally have.
By using and modifying the existing na.sample agent code, you streamline the agent writing process. At this point, na.sample is your new agent. To complete the process, rename the agent as appropriate and follow the process described in Section 13.1.9, "Assign an Agent Name," on page 13-6 .



[Top] [Prev] [Next] [Bottom]

Copyright 1996 Sun Microsystems, Inc., 2550 Garcia Ave., Mtn. View, CA 94043-1100 USA. All Rights Reserved