User Guide for LISA

From EGEE-see WIki

Jump to: navigation, search

Contents

LISA

LISA (Localhost Information Service Agent) is a lightweight framework that can help in optimizing other applications by means of monitoring services. It consists of a number of service agents that can be easily deployed on any workstations, independent of the local architecture or operating system. The architecture of the framework is represented in the following figure:

Image:cipsm_lisa_arhitecture.png

The framework consists of a number of Service Agents that are deployed on the stations that are to be monitored. A Service Agent automatically detects the architecture on which it is deployed and is capable to dynamically load the binary modules necessary to perform monitoring services. LISA is based on Java technologies, those any agent can operate independent of the operating system platform.
The service agent is composed of a core system and a set of deployable modules. The core system is responsible for managing monitoring modules in the framework. It dynamically discovers available modules and can load or unload them at any time. The modules can be loaded or unloaded without the need of restarting the system and without affecting the rest of the running modules. Another interesting feature of the engine is that is capable of sensing when parameters are updated. When such updates occur the engine is capable of triggering sets of actions such as reloading the modules or updating the running parameters.
The core system is capable of working with remote repositories of modules. From remote locations it can dynamically trigger updates of module. Also the core system provides the capability of integrating an agent with any external application using sets of exported API methods. Every module in the system can export such functionality to any external application.
Another aspect of the implementation of the core system is that it is permanently monitoring itself. This capability makes LISA capable of detecting potential problems and choosing from a number of possible solutions to counteract them. For example if a loaded module stops due to some running conditions or programming fault the core system is capable of dynamically restarting it. The core system is even capable of restarting the agent altogether, for example when the Java virtual machine encounters a problem such as the available memory wears out or the running threads come to a deadlock condition.
The LISA Service Agent incorporate a number of monitoring and controlling modules that can be used to completely supervise the end-user system, the monitoring ranging from the hardware architecture and the running parameters of the workstation to the local networking capabilities. New modules can be easily constructed and added by users and this capability provides increased monitoring functionality to the system.
One of the already implemented and available modules offers complete system monitoring functionality. This module can monitor environmental information such as the type of the operating system and version, the user under which LISA runs, JVM version, local IP address or, even better, if the address is a private one it can retrieve the public address of the access point for the local network, and the AS to which the station belongs. This module is also able to monitor the health of the LISA agent using JMX technology. One other available monitoring module supervises the status of the workstation health parameters. The monitored information is classified into information regarding the CPU utilization (how much of the CPU is used by the user applications, by the system, how much is idle, etc.), regarding memory utilization (free memory and total available memory in the system, page swapping), regarding disk usage (free disk space and total available disk space), regarding the current load of the system (load1, load5, load15) or the number of running processes and information regarding networking (in and out traffic for each available network interface). This module is dynamically adjusting itself based on the architecture of the system counters of the underlying workstation on which LISA is running.
LISA also comes with a module that monitors the hardware configuration of the local workstation. The information collected by this module is independent of the underlying operating system offers.
One other module is capable of monitoring adjusting the networking performances of the local station. This module reads information regarding the network interfaces available in the system such as traffic patterns, addresses and capacities and current settings. Also it monitors specific parameters related to the installed TCP/IP stack. It reads parameters regarding the health of the IP protocol (packets sent and received, bad packets, resent packets, etc.), the health of the TCP protocol (sent and received segments, fragmentation, etc.) and the health of the UDP protocol. This module is also able to adjust on user request some of the parameters that are used for tunning the TCP/IP stack in order to optimize the networking performances of the local workstation.
One other implemented module can be used as a shell interface between any user having correctly defined rights and the local workstation on which the LISA agent is deployed. Using this module the user, meaning the user running the GUI client or an external application, can execute commands and read the returning outputs. And another module is able to test the available networking throughput between the local workstation and any other remote station, using IPERF.
All these modules are meant to make LISA an application which best describes the local station in order to help other applications optimizing performances by means of monitoring services. There are actually two defined ways in which an application can integrate the LISA services.
Any application can use the API functions that each module is exporting. In this way any application can be tightly coupled with the LISA framework. Another modality is that the application connects itself to the service daemon. In fact this is how LISA is connecting to the higher level MonALISA monitoring services and also this is how the GUI client connects to the LISA services. In both approaches the LISA agent is sending to the registered interfaces the monitoring results and can receives commands to trigger internal actions.
The lightweight monitoring framework also consists of a GUI client which can be used by any user to keep track of the current monitoring results. The GUI client is using Java WebStart technology and because of that it can be executed on any local station. The GUI client can connect to any number of LISA services using networking communication. In this way the status of the services is monitored from any remote location. When connecting to a remote monitoring agent the user running the GUI client have to authenticate in order to provide a set of rights. Based on this access rights the user can have only the capability to receive results or it can more advance rights such as to modify running parameters or rights to start or stop modules.
Each module loaded in the LISA framework is able to define a graphical interface. The client does not start with any of the graphical interfaces defined by any of the running modules. All these interfaces are dynamically loaded into the GUI client. The interfaces can be loaded from any remote locations, from repositories of modules or even can be loaded from the agent itself where a particular module is running. When the user connects to a running LISA agent it will not receive from the beginning all monitored results. When the user becomes interested in a particular module (by clicking on that particular module) the dynamic interface is loaded in the GUI system and the client starts receiving monitoring results.
The client is able to preserve the list of known LISA agents from one run to another. This combined with the aspect that the GUI client is able to remember certain repeated actions means that the system is designed to be user-friendly. Because LISA is a part of MonALISA there is another module which sends the monitored values back to one or more MonALISA service. For that this modules uses the ApMON module provided in the MonALISA framework. The MonALISA framework can help in the way that the monitored result values can be processed at a higher level using the MonALISA inner functions.


User Guide for LISA

In order to use LISA you need to install the Service Agents on the stations under supervision. After configuring the services (specify what modules are to be loaded) you can use either the LISA GUI client or the MonALISA client to monitor the stations remotely (the clients act as central points of commands for the entire framework).
The LISA GUI client is accessible at -- http://monalisa1.cern.ch/~lisa2/client/lisa.jnlp (JavaWebStart application). After starting the client you can connect to a Service Agent by selecting from the "Options" menu the "Add host..." option. This options opens a dialog where you can enter the following data. "Host address" represents the address where the agent is running. "Host port" represents the port on which LISA is listening for incoming connections (TCP port, set in the configuration of the agent, default 11000). Next you can choose what type of log in do you want. LISA accepts two different types of users. The "view only" user is able only to monitor the stations, without being able to commit any controlling commands to the station. This is why the authentication of this type of user is weaker (username and password only). On the other hand, the admin type of user is able, beside monitoring the stations, to conduct certain commands on the remote station. For example, the admin user is capable of stopping or starting instances of the running modules, to load or unload modules, to execute commands on the remote stations, to change the configurations of the Service Agent dynamically, to modify parameters related to the networking capability of the remote stations, etc. For this reason, the authentication of the admin user is based on certificates. So, in order to authenticate, you need to choose the type of user (the radio buttons in the dialog) first. Depending on the type of user selected, you need to enter the login information required. When completed, simply press the ok button (or Enter)
If the authentication succed, in the tree panel in the left, under the "LISA2 Hosts", you will see a new branch, having the name "<address:port>". Under this, you see all currently running instances of the loaded modules.
When selecting the "<address:port>" entry you are presented with the configuration panel of the LISA agent. The panel is composed of four tabs: the module panel, the parameters config panel, the url panel and the log panel.
The module panel presents the list of currently loaded modules (in memory modules). If the user is logged under view mode he is only presented with the list of modules. However, the admin user can load or unload and start new instances of the loaded modules. However, some modules can only have one instance running at any given time, so if an instance of one such module is already started the start instance button is inactivated. The icon on the left of each module is used to specify the type of module (push or pull).
In the configuration panel you are presented with the current global configuration of the Service Agent, in the form of a table. Each row of the table specify the data configuration for one parameter, in the form (name, value, type and description). The parameters are explained in the install guide page. The parameters are also grouped in several sections: the core section presents the parameters related to the Service Agent, the farm section specify the information related to the host, and logger specify the logging parameters. The view only user can only see the current values of the parameters. The admin user can alter their values too, by simply clicking on a value and editing its content (just press Enter when done entering the new value). The Service Value reconsiders itself dynamically and takes into account the new specified value.
The updates panel presents the list of URLs used to load modules. The modules are dynamically loaded from a remote website, over http, and the URL of the desired update sites are specified in this panel. The modules are loaded based on JavaWebStart technology, meaning the Agent is capable of detecting when a module is updated and load the new version automatically. The view only user is only presented with the list of URLs in use, while the admin user can alter the list by adding or removing URLs. The URLs are considered only when the parameter net_update is set to true.
Finally, the logs panel presents the logging of the Agent, the messages generated on the remote site (related to the current behavior of the Agent, the status of the modules, possible warnings generated by modules...). This panel is mainly used for debugging purposes.
The instances of the modules are presented in the tree under the main node representing the Agent. The GUI client does not come with a set of GUI interfaces for each module. Instead, the interfaces are dynamically loaded at runtime. So, when clicking for the first time on a module the LISA client will automatically download its correspondent GUI interface. The information presented in each interface depends on the capability and purpose of each module. Again, depending on the type of logged user, the interface can act differently. For instance, the BashModule interface does not allow a view-only user to execute commands at all, the NetworkingModule presents the monitoring information related to the network stack to the view-only user, but does not allow him to change any networking related parameters of the remote workstation.
Each module, in addition to having a GUI interface to present the results, also considers several running configuration parameters. In the LISA GUI client the parameters are also presented when clicking on a running instance of a module (the Parameters tab).
The system module presents the information related to the status of the station. In addition, the parameters report.class, report.memory, report.summary and report.threads are set to true the module presents information related to the status of the classes, memory or thread usage at the application level (the memory usage of the Agent,...):

Image:cipsm_lisa_system.png

One other available monitoring module supervises the status of the workstation health parameters. The monitored information is classified into information regarding the CPU utilization (how much of the CPU is used by the user applications, by the system, how much is idle, etc.), regarding memory utilization (free memory and total available memory in the system, page swapping), regarding disk usage (free disk space and total available disk space), regarding the current load of the system (load1, load5, load15) or the number of running processes and information regarding networking (in and out traffic for each available network interface). The information presented in the GUI by this module is organized in several groups, corresponding to several tabs. The first tab panel presents a general status of the workstation. The next tabs present the cpu, pages, memory, disk, diskio, load or network related monitored results. The results are presented in the form of plots. The plots show the last values versus the time the values were collected. The results are presented in real-time, but the GUI also allows the user to view a history of the collected results from the time the specific LISA agent was started. The plots can be customized very easily using the buttons available in the bottom-left corner of the plot. The user can therefore change the colors (background and series) and also can zoom in corresponding coordinates. A example of the functionality offered by this module is presented in the following figure:

Image:cipsm_lisa_host.png

One other module is capable of monitoring adjusting the networking performances of the local station. This module reads information regarding the network interfaces available in the system such as traffic patterns, addresses and capacities and current settings. Also it monitors specific parameters related to the installed TCP/IP stack. It reads parameters regarding the health of the IP protocol (packets sent and received, bad packets, resent packets, etc.), the health of the TCP protocol (sent and received segments, fragmentation, etc.) and the health of the UDP protocol. This module is also able to adjust on user request some of the parameters that are used for tunning the TCP/IP stack in order to optimize the networking performances of the local workstation. Examples of the capabilities offered by this module are presented in the following figure:

Image:cipsm_lisa_networking.png

The client is able to preserve the list of known LISA agents from one run to another. This combined with the aspect that the GUI client is able to remember certain repeated actions means that the system is designed to be user-friendly. Because LISA is a part of MonALISA there is another module which sends the monitored values back to one or more MonALISA service. For that this modules uses the ApMON module provided in the MonALISA framework. The MonALISA framework can help in the way that the monitored result values can be processed at a higher level using the MonALISA inner functions.

Installing guide

In order to install the LISA agent you need to download locally and execute the install.sh script:

     wget http://monalisa1.cern.ch/~lisa2/install/install.sh
     chmod +x install.sh
     ./install.sh

The script downloads and installs all the necessary modules locally:

     You are installing LISA as an unprivileged user. Some functionality may be disabled in this mode
     
     Specify location where Lisa Agent will be installed ? [/home/user/] : /home/user/lisa/
     Installing Lisa Agent into '/home/user/lisa' ...
     16:24:39 URL:http://monalisa1.cern.ch/~lisa2/install/LISAv2.tgz [9998628/9998628] -> "/home/user/LISAv2.tgz" [1]
     
     Use /home/user/lisa/bin/servicectl to control LISA agent

Prior to starting the agent you need to configure its runtime parameters. Go to the location where LISA was installed (we refer to as {LISA}) and modify the file {LISA}/conf/config.ini. In this file the parameters are grouped in several groups, depending on their functionality. The parameters are also described in the comments associated with each one.

The Core section describes the global runtime parameters of the agent. net_update specifies whether or not the agent should check the web periodically for available updates (to the modules, but also to the agent core runtime code itself). It this parameter is set to true, then update_mod_urls will specify the list of URLs that are periodically checked (you can use one of the supplied values, but you can also easily deploy new repositories with module (just follow the JavaWebStart/JNLP syntax). Identical, the update_base_urls parameters specifies the list of URLs to be used for periodical checkings of the agent core updates. The keystore_locations parameter specify the location of a trustore file that is used to check the loaded modules against. In order for a module to be successfully deployed for execution on the agent it must be packed in a module (jar file) signed by a valid certificate. The signature is checked against the repository specified by this parameter. If missing, then the jars are not checked at all. The keystore_pass and keystore_alias parameters are used to specify the optional alias and password values to access the trustore file. The method_timeout parameter specifies the maximum time to wait for the execution of a method. If set to a value >0 then the agent executes the various methods of the loaded modules and waits for this maximum specified amount of time. This is usefull to detect endless loops and other problems in the code of the third-party users (mainly modules loaded at runtime). The crawler_sleeping parameter specifies how often the web crawler, the thread that periodically checks for available updates, should prone the URLs for any updates. This parameter is only considered when the net_update is set to true. The service_name specifies the name of the agent to be used.<p> <p align=justify>The Logger section specifies parameters related to logging. LISA uses the java logging support, with parameters specifying the level of logging to be used.

The rest of the configuration file consist of parameters related to the various modules. Each module that should be executed is specified in a different section. A section consisting of [module ModuleName] declares a new module instance to be executed by the agent. Any such section specifies at least a class name to be loaded (the class parameter) and a name (the name parameter) under which the module will be seen in the GUI client.

A special module is the LISA Daemon, which is responsible with the incoming connection requests of the GUI clients. This module acts as a server for the incoming connections, it sends the results coming from the running modules to the clients and sends the command requests of the clients to the corresponding destination modules. The module is by default set to be loaded in the configuration file. The class parameter of the module is set to be lisa.daemon.LisaDaemon. The section also contains a number of other parameters: The valid_clients specifies the name of a file containing the list of allowed view-only clients that can connect to the agent. The file specifies the list of clients in the form username password, one entry per line. Only the view-only clients specified in this file will be allowed to connect to the agent from the GUI and inspect the results. For the admin type of users that are allowed to connect from the GUI to the agent the user need to specify the admin_trustore parameters. The admin users must connect using valid certificates that are checked against the trustore repository specified by this parameter. Optional, the trustore file can be also protected by a password, in which case the user also needs to set the corresponding admin_trustore_password parameter accordingly. The daemon_port parameter specifies the port on which the daemon will listen for incoming connections from the GUI clients. By default this parameter is set to 11000. The ssl_keystore and ssl_password parameters specifies the SSL related protocol preferences that the daemon will use for the connections with the GUI clients (the channels are secured using the SSL protocol). In the GUI client the interfaces corresponding to various running modules are loaded dynamically at runtime. The interfaces are loaded from a remote web repository. In addition, the daemon can also instantiate a web server from where it will dynamically serve the instances of the running modules to the remote GUI client. To set this as well the user must specify the parameter lisa_http_port, a port on which the daemon will listen for incoming http requests for interface downloads. Optional, the web repository can log messages in the files specified by the lisa_http_error_log and lisa_http_access_log parameters.

The rest of the modules specifies different parameters related to their running behavior. For example, for the SystemMonitorModule (that is instantiating a class=lisa.modules.system.SystemMonitorModule class) the user can specify the following parameters. The run_time parameters specifies how often the module should collect the data (default to 30 seconds). The report_threads, report_memory, report_class and report_summary parameters specify whether the system module should report data corresponding to the running threads, memory allocations, loaded classes or a summary of the runtime parameters. The gui_codebase parameter specifies the URL from which a GUI client should download at runtime the interface corresponding to this module. The way each module is constructed instruct its author to specify some codebase for the GUI interface, so if this parameter is not specified then the module will default its value to the one specified by the module itself. This parameter is useful if the user wants to change the codebase location specified in the module dynamically at runtime, without rebuilding the entire module. The gui_class specifies the name of the GUI class that must be loaded by the GUI client to present the results corresponding to this module.

After setting the configuration file you can start the agent using the {LISA}/bin/servicectl script. If everything is well, than you will see something like:

     user@machine:~/lisa/bin$ ./servicectl start
     Starting LISAv2 Agent ...   [ OK ] (PID = 1161)

In the {LISA}/bin/log folder you can also inspect the log messages associated with the running LISA agent. The main log file is ML0.log. Here you can inspect for messages related to the loaded or discovered module, eventual thrown exceptions, etc. The major problems are reported in the LISA.log file. If you specified the daemon module to start, you can even proceed directly with the GUI client to inspect the monitored results. In addition, you do not need to restart the LISA agent for changing parameters. Simply modify the config.ini file and the agent will automatically load its new configuration. Or simply change the keystore, trustore or password files that are used by the agent and it will automatically load its new list of users. The LISA agent will not even need to be monitored for failures. When problems are detected by the agent (deadlocks, out-of-memory) it will automatically restart itself, without the intervention from the user. If you want to unload/stop a running module or simply instantiate a new module just remove or add the corresponding information in the configuration file.


Deploying custom modules

The classes corresponding to a new module should be declared in a different package for easy packing. In this example we will presume the classes corresponding to our custom module resides in lisa.modules.custom package.The main class that is loaded should be declared as:

     public class CustomModule extends PullModule implements SingleInstanceModule, LisaModule, 
     LisaXDRModule, PropertiesChangeNotifier {

The class extends the monalisa.modules.PullModule class. The custom class should collect monitoring results to be sent to various clients. You can write a module that does not collects results, but instead only reacts to commands comming from various clients by extending instead the monalisa.modules.Module class. The class implements the monalisa.modules.SingleInstanceModule interface, specifying that at any given time at most one instance of this module could be instantiated. It also implements the monalisa.modules.LisaModule interface, preparing to declare a module that not only is capable to collect monitoring results, but that can also present a GUI interface and be used in conjuction with the GUI client. In addition, out custom class implements the monalisa.modules.LisaXDRModule interface, meaning it is prepared to accept commands and export results to some custom client through the XDR protocol. Finally, the module implements the monalisa.util.PropertiesChangeNotifier interface, meaning it can take actions when some parameters related to the module are dynamically changed at runtime. The next step in implementing the custom module is to implement the following methods.

     public void start() {
           prop.registerNotifier(this);
           setStatus(Module.STATUS_RUNNING);
           logger.info("[CustomModule started]");
     }

The start method is called when an instance of the module is first loaded. In this example, the module will register for changes of the running parameters to the <i>MLProperties prop. The MLProperties represents the list of known parameters. For instance, if the module wants to question the value of a parameter, it can issue an instruction like:

     boolean ret = prop.getb("param", true, "A boolean value");

In this example, the getb method always returns a boolean value. There are methods for different types of results implemented in MLProperties. The method receives as parameters the name of the parameter whose value is returned, a default value that is used in case the user did not define the parameter and a comment. If the parameter is not defined in the configuration file, than it is automatically added with its default value and with a comment as specified by the third parameter. Next, the method setStatus declares the current status of the running module, an informative method that publishes the status for future inspections and is optional. Finally, the third instruction writes an informative message in the log.

     public void stop() {
           prop.unregisterNotifier(this);
           setStatus(Module.STATUS_STOPPED);
           logger.info("[CustomModule stoped]");
     }

The stop method is called when the module is stopped. In this example, it will unregistered the instance as a listener for changes of the configuration file, it will set its status accordingly and output an informative message in the logs.

     public boolean canRun() {
           return System.getProperty("os.name").contains("Linux");
     }

The canRun method is called prior to the starting of the module. The method returns a boolean value that specifies if the module is allowed to run on the localstation or not. In this example the module will only be loaded if the agent is running in Linux.

     public String getGUIClass() {
           return prop.gets("gui_class", "lisa.gui.custom.CustomPanel", "The base class for the gui interface");
     }

The getGUIClass method specifies the name of the class that should be loaded dynamically in the GUI client. The best way is to return the runtime configuration parameter gui_class so that to allow the user to optionally modify this at runtime. In addition, you need to declare the class lisa.gui.custom.CustomPanel

     public String getCodebase() {
           String location = Core.getLoader().getJarLocation("custom.jar");
           if (location == null)
                 location = "N/A";
           else
                 location = location + "custom_gui.jar";
           String others = Core.getLoader().getJarLocation("jcommon-0.9.0.jar");
           if (others != null)
                 location += ";" + others + "jcommon-0.9.0.jar";
           return prop.gets("gui_codebase", location, "The codebase for the gui interface");
     }

The getCodebase method returns the codebase, the address from where the GUI client should download the interface corresponding to this module. In this example, we first interogate the location of the module package (we will pack our custom module in a package called custom.jar). We prepare to specify that the GUI interface of the module resides in the same location, but is called custom_gui.jar. Next we specify that the module also depends on one other library, jcommon-0.9.0.jar and specify that this should also be loaded by the GUI client for the module's interface to be able to run. You can specify as codebase a list of such libraries, separated by ;. Finally, we specify that this should also be added in the configuration file so that the user can dynamically modify the list at runtime without rebuilduing the module.

     public void notifyMessage(String peer, String sessionID, boolean isAdmin, Object msg) {
           if (msg == null) return;
           if (!isAdmin) {
                 Core.getCore().addData(peer, sessionID, this, new Integer(-1));
                 Core.getCore().addData(peer, sessionID, this, 
                 "Unauthorized access. Only administrators can execute commands\n");
           return;
           }
           if (msg instanceof String) {
                 execute((String)msg, peer, sessionID);
           }
     }
     
     private void execute(String command, final String peer, final String sessionID) {
           // perform the execution of the command given as argument
           String path = prop.gets("command_path", "/bin,/sbin,/usr/bin,/usr/sbin",
                 "The path to execute various commands");
           if (path != null && path.length() != 0)
                 path = path.replace(',', ':').trim(); 
           CommandResult cmdRes = cmdExec.getInstance().executeCommandReality(
                 command, null, 5 * 60 * 1000L, path);
           String ret = cmdRes.getOutput();
           if (cmdRes.failed() || ret == null || ret.length() == 0 
           || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) {
                 Core.getCore().addData(peer, sessionID, this, new Integer(-1));
                 Core.getCore().addData(peer, sessionID, this, "Error");
                 return;
           }
           Core.getCore().addData(peer, sessionID, this, new Integer(0));
           Core.getCore().addData(peer, sessionID, this, ret);
     }

We declare next the notifyMessage method. The method is called when the client interface sends data request back to the module, for instance to execute a command like in this example. In the example we first verify if the user requsting the execution has sufficient permissions to execute a command in this module. In this example we expect the user to have admin privileges. If the user has a view-only role, than we send back to the interface an error code (-1) and a message specifying why the execution failed. We next verify the message object transmitted as parameter is indeed a String as expected. The message argument is specified as an Object, so that you can send any serialized type of information. If everything is fine we next proceed to the execution of the command. For that we declared a private method called execute. In this method we first set the path environment for the command, using again the runtime configuration file for later runtime modifications. To execute commands you can use the cmdExec provided class, like in the example. The method executeCommandReality receives as arguments the command to be executed, an optional expected string (if specified, the command will run to its completion, until timeout or until it encouters this text), a timeout (how long to maximum wait for the execution) and the environmental path to be used for execution. The method returns a CommandResult type of result that contains the output and also the status of the execution. We question the status of the execution (the method failed and check also a possible error (the "unknown command" possible return, which in some cases can still result in an zero status code of the command). Notice an interesting construction here, the use of the PatternUtil class. This class can be used as a repository of compile Pattern expresions to be used by various modules. You can use it for instance to check the output of various executed commmands against matching of various patterns. In the end, the method will send back to the client the status of the execution and a message. The example also presents how the module should report back commands to the client. This is accomplish in an asynchronous manner, by sending the returned results back with the help of the method Core.getCore().addData. The peer specifies the name of the client receiving the results, and the sessionID identifies a precise session with one particular client. Next come two methods that inform the module when a new peer client connects or disconnect, usefull for instance for sending results as soon as possible to some clients, without waiting for the next monitoring cycle:

     public void peerStarter(String peer, String sessionID) {
     }
     
     public void peerStopped(String peer, String sessionID) {
     }

In this example we are not interesting in doing something in particular when a client connects/disconnects.

     public List<String> getCommandSet() {
           ArrayList<String> list = new ArrayList<String>();
           list.add("exec");
           return list;
     }

We next must declare the method getCommandSet. This commands returns the set of commands that can be received from a client connecting using the XDR protocol. In this case we specified that the module is able to execute commands starting with exec.

     public String getCommandUsage(String command) {
           if (command.equals("exec"))
                 return "exec [command]";
           return "unknown command "+command;
     }

The method getCommandUsage is called when an XDR client issues a help. It returns a string explaining the usage of a particular type of command. The actual execution of a command received from an XDR client is declared in the method:

     public XDRMessage execCommand(String command, List<String> args, AuthorizationToken acl) {
           if (command.equals("exec")) {
                 if (args == null || args.size() == 0)
                       return XDRMessage.getErrorMessage("command usage: exec command");
                 final String toExec = args.get(0);
                 try {
                       cmdExec.CommandResult ret = cmdExec.getInstance().
                             executeCommandReality(toExec, null, null);
                       // construct a response...
                       if (ret.failed())
                             return XDRMessage.getErrorMessage(ret.getOutput());
                       return XDRMessage.getSuccessMessage(ret.getOutput());
                 } catch (Exception e) {
                       return XDRMessage.getErrorMessage("got exception " + e.getLocalizedMessage());
                 }
           }
           return XDRMessage.getErrorMessage("unknown command " + command);
     }

In this example we verify that enough arguments were given to execute the command and then proceed to the actual execution. We can return back an error message by calling the method XDRMessage.getErrorMessage or a successful message by calling the method XDRMessage.getSuccessMessage.

     public void propertiesChanged(String sKey, Object oVal, MLProperties prop) {
           if (sKey == null || oVal == null)
                 return;
           if (sKey.equals("gui_codebase")) {
                 restart();
           }
     }

One other method to be declared is propertiesChanged. This method is called when one parameter related to the running behaviour of this module is changed at runtime. We already declared that some parameters like command_path for example are question dynamically each time we need to set their value, so we do not need to worry about their change trapping in this method. As an example though we declared what should happened when the gui_codebase parameter changes. In this case we should restart the module by calling the method restart inherited from Module that triggers a restart of the module, so that the interface is force to reload in the remote GUI client.

     public Result getData() {
           Result res = new Result();
           res.add(prop.getDefaultCluster("localhost", true), 
                 prop.getDefaultNode("localhost"), "time", 
                 new MLDouble(System.currentTimeMillis()));
           res.add(prop.getDefaultCluster("localhost", true), 
                 prop.getDefaultNode("localhost"), "user", 
                 new MLTextSystem.getProperty("user.name")));
           Core.getCore().addData(this, res);
           return res;
     }

The last method to implement is getData. This module is called at regulat time periods and should perform the monitoring functionality, collect and return some data. In this example we started by first constructing a Result object that must be returned. We then added two types of data: the current time, as a double, and the name of the user running the agent, in the text form. The method add receives as arguments the name of the cluster and node under which to publish the data (as in the case of MonALISA, in fact the LISA can publish this data using ApMON in the MonALISA farm), the name of parameter to be published and its effective value. For the value LISA provides a set of different types of results, such as floats, integer, text, image, etc.

After implementing our custom module we can proceed to writing the custom GUI interface to present the data. We declared that the interface will be declared in the lisa.gui.custom.CustomPanel class.

     public class CustomPanel extends LisaRemotePanelInterface {

The custom interface must extend the abstract class lisa.gui.LisaRemotePanelInterface. Next we need to add the corresponding methods:

     public void notifyObject(Object result) {
           if (result instanceof Integer) {
                 // present the code of error in the panel
                 return;
           }
           if (result instanceof String) {
                 // present the returned string to the user in the GUI panel
                 return;
           }
           if (result instanceof Result) {
                 Result res = (Result)result;
                 TreeMap<String, TreeMap> map = res.getData();
                 for (Map.Entry<String, TreeMap> entry : map.entrySet()) {
                       String farm = entry.getKey();
                       TreeMap<String, TreeMap> map2 = entry.getValue();
                       for (Map.Entry<String, TreeMap> entry2 : map2.entrySet()) {
                             String cluster = entry2.getKey();
                             TreeMap<String, TreeMap> map3 = entry2.getValue();
                             for (Map.Entry<String, TreeMap> entry3 : map3.entrySet()) {
                                   String node = entry3.getKey();
                                   TreeMap<String, Object> params = entry3.getValue();
                                   for (Map.Entry<String, Object> p : params.entrySet()) {
                                         if (p.getValue() instanceof MLDouble) {
                                               MLDouble d = (MLDouble)p.getValue();
                                               // present the time by using d.getVal()
                                         }
                                         if (p.getValue() instanceof MLText) {
                                               MLText t = (MLText)p.getValue();
                                               // present the text by using t.getVal()
                                         }
                                   }
                             }
                       }
                 }
           }
     }

The notifyObject method is called when a new serialized object is received from the module, either a new monitoring result or the result of an executed command. In this example we can receive either the result of a commands (an Integer or String result), or a monitoring data result (a Result object). The method should simply present in some intuitive way to the user the received data.

     public void notifyPropertyChanged(String key, String value) {
     }

The method notifyPropertyChanged is called when some running parameters of the module change. This is useful for propagating changes to parameters affecting the running GUI instance. In the table showing the parameters in the GUI client the results are automatically visual updated when a change occurs.

     public void setParent(JFrame parent) {
     }

The method setParent is called in order to specify the parent frame object so that the module can keep a reference to it (for modal dialogs for example).

     public ImageIcon getModuleIcon() {
           try {
                 String imageName = "/lisa/gui/custom/custom.png";
                 URL iconURL = this.getClass().getResource(imageName);
                 // Build and return a new ImageIcon built from this URL
                 return new ImageIcon(iconURL);
           } catch (Exception ex) {
                 return null;
           }
     }

The method getModuleIcon should return an ImageIcon that is presented in the GUI tree of modules when this interface is loaded. In this example we assumed that the module also includes a custom.png image to be dynamically displayed.

This is the list of all methods to be implemented. Of course, you need to specify the panels and the way the information is presented to the user. In addition, in order to send back commands to be executed by the modules, you could use a method like:

     private void executeCommand(String command) {
           ClientChannel c = getClientChannel();
           if (c == null) {
                 System.err.println("Error: the channel is not connected with the remote LISA daemon...");
                 return;
           }
           try {
                 c.sendMsg(getInstanceName(), command); // send back request in the form of a string....
           } catch (Exception ex) {
                 System.err.println("Got exception "+ex.getLocalizedMessage());
           }
     }

In this example, you first need to get a handler to the channel with the LISA agent by using the method getClientChannel inherited from LisaRemotePanelInterface. Next, using the method sendMsg you simply send a new message to the agent, message that will be multiplexed to the proper module and executed there. As probably noticed, the entired execution is asymetrical in the way that the result of the command execution is delivered by another thread that called notifyObject (this is because you do not want to generally block the AWT thread waiting for the incomming result).

Next you only need to pack the class CustomModule in custom.jar, pack CustomPanel and custom.png in custom_gui.jar and sign both jars with a valid certificate.After doing this, simply add a new entrance in the config.ini: [Module Custom]...class=lisa.modules.custom.CustomModule

Personal tools