Wednesday, May 14, 2008

Writing a RHQ plugin Part 3

Welcome to the third article in the "Writing a RHQ plugin" series.

In the last post I was talking about the plugin descriptor, discovery component and the plugin component. Today we will see the project structure, some code and how to build and run our plugin.

Remember: for the start we just have a very simple version of the plugin. We will perhaps enhance it in a future posting.

First let's talk about the project structure in the file system.

The RHQ project structure



To make things easier, we will host this plugin just within the RHQ tree. So go and check out RHQ from
http://svn.rhq-project.org/repos/rhq . Build the project as described on the build page on the wiki. After that is done, we will start to add our plugin into modules/plugins/.

Directory layout



Create the following directory structure:




Add modules/plugins/httptest/src/main/java to the build path in your IDE.

The classes within org.rhq.plugins.httptest form the plugin discovery component and plugin comonent and will be described below.


Maven pom


RHQ is a mavenized project, thus we need to supply a pom file. Easiest is to just grab another pom, copy it over to the root of the plugin subtree and change at least the artifactId:


<groupId>org.rhq</groupId>
<artifactId>rhq-httptest-plugin</artifactId>
<packaging>jar</packaging>
 
<name>RHQ HttpTest Plugin</name>
<description>A plugin to monitor http servers</description>


Please note that this only defines the pom for this subtree - it will not add this to the global project. To do this, you need to add the httptest plugin to the parent pom at the modules/plugins/ level:

  <modules>
<module>platform</module>
...
<module>postgres</module>
<module>httptest</module>
</modules>



The artifacts



We will now look at the individual three artifacts that make up a plugin. The directory tree above shows where they are located.

Plugin discovery component



First we start with discovering our server. This is relatively simple and directly
follows the description in the previous part.


public class HttpDiscoveryComponent implements
ResourceDiscoveryComponent
{
public Set discoverResources(ResourceDiscoveryContext context)
throws InvalidPluginConfigurationException, Exception
{
Set<DiscoveredResourceDetails> result =
new HashSet<DiscoveredResourceDetails>();
 
String key = "http://localhost:7080/"; // Jon server
String name = key;
String description = "Http server at " + key;
Configuration configuration = null;
ResourceType resourceType = context.getResourceType();
DiscoveredResourceDetails detail =
new DiscoveredResourceDetails(resourceType,
key, name, null, description,
configuration, null
);
 
result.add(detail);
 
return result;
}
}


Again it is extremely important that the key is/stays the same for each discovery performed!

Plugin component



So the next part is the plugin component to do the work


public class HttpComponent implements ResourceComponent,
MeasurementFacet
{
URL url; // remote server url
long time; // response time from last collection
String status; // Status code from last collection

As we want to monitor stuff, we need to implement the MeasurementFacet with the getValues() method (see below).

But first we implement two of the methods from ResourceComponent. The first returns the availability of the remote server. We check if the status is null or 500 and return DOWN, otherwise UP.

public AvailabilityType getAvailability()
{
if (status == null || status.startsWith("5"))
return AvailabilityType.DOWN;
return AvailabilityType.UP;
}

One needs to be careful here, as the discovery will not happen as long as this method is returning DOWN. So we provide a valid start value in the start() method from the ResourceComponent:

public void start(ResourceContext context) throws
InvalidPluginConfigurationException, Exception
{
url = new URL("http://localhost:7080/");
// Provide an initial status,
// so getAvailability() returns UP
status = "200"; so getAvailability() returns up
}

Analogous to start() there is a stop() method, that can be used to clean up resources, which we leave empty and don't show it here.

This leads us to getValues() from the MeasurementFacet:


public void getValues(MeasurementReport report,
Set<MeasurementScheduleRequest> metrics)
throws Exception
{
getData();
// Loop over the incoming requests and
// fill in the requested data
for (MeasurementScheduleRequest request : metrics)
{
if (request.getName().equals("responseTime")) {
report.addData(new MeasurementDataNumeric(
request, new Double(time)));
} else if (request.getName().equals("status")) {
report.addData(new MeasurementDataTrait
(request, status));
}
}
}

We get data from the remote and then loop over the incoming request to see which metric is wanted and fill it in. Depending on the type we need to wrap it into the correct MeasurementData* class.
This leaves the implementation of getData():

private void getData()
{
HttpURLConnection con = null;
int code = 0;
try {
con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(1000);
long now = System.currentTimeMillis();
con.connect();
code = con.getResponseCode();
long t2 = System.currentTimeMillis();
time = t2 - now;
} catch (Exception e) {
e.printStackTrace();
}
if (con != null)
con.disconnect();
 
status = String.valueOf(code);
}


This is nothing fancy again. Just open a URL connection, take the time it takes to connect, get the status code and we are done. Of course, this could be optimized, but for this article I wanted to use a simple solution.

Plugin descriptor



The plugin descriptor is where everything is glued together. First we start off with some "boiler plate" code:

<?xml version="1.0" encoding="UTF-8" ?>
<plugin name="HttpTest"
displayName="HttpTest plugin"
package="org.rhq.plugins.httptest"
version="2.0"
description="Monitoring of http servers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:xmlns:rhq-plugin"
xmlns:c="urn:xmlns:rhq-configuration">


The package attribute predefines the Java package for Java class names that appear later in the descriptor.

   <server name="HttpServer"
discovery="HttpDiscoveryComponent"
class="HttpComponent"
description="Http Server"
>


We define our plugin as a Server. From the intuition it could be a Service, but Services can't just live on their own so we choose a server here. The attribute class denotes the plugin component and discovery the discovery component. If you have specified the package above, you can just use the class name without prefix.

    <metric property="responseTime"
displayName="Response Time"
measurementType="dynamic"
units="milliseconds"
displayType="summary"/>

<metric property="status"
displayName="Status Code"
dataType="trait"
displayType="summary"/>
</server>
</plugin>


Now the two metrics. With all the knowledge you have now, they are nothing special anymore.
Again, responseTime is modeled as numerical data, while the status is modeled as trait. This could have been done differently, but is done here for educational purposes :-)


Ready, steady, go ...



To compile the plugin, go to the root of the plugin tree and do
mvn -Pdev install
The dev mode allows maven to automatically deploy the plugin to a server instance as described on the Advanced Built Notes page on the RHQ-Wiki.

When the server is running or starting up, you will see a line like this in the server log:


21:49:31,874 INFO [ProductPluginDeployer] Deploying ON plugin HttpTest (HttpTest plugin)


The next step is to make the plugin available to the agent. Remember that the agent is usually pulling plugins from the server when it is starting up. So if you have not yet started the agent, there is nothing to do for you. If the agent is already started, you can issue plugins update at the command prompt to update them to the latest versions of the server.

If you now log into the GUI, go to the resource browser and click on 'Servers', you can see the server discovered by our plugin:



Clicking on the server name or the little 'M'onitor icon leads you to the indicator charts, where you can see the response time values:



When you click on the Metric Data subtab, you can see the raw data for the server:



On the top you see the numerical ResponseTime data, and in the lower section the server status as trait as expected.

Summary



Congratulations, you just wrote your first RHQ plugin, that can also be used in JBoss ON 2.0. Writing a plugin consists of three parts: Discovery, Plugin Component and plugin descriptor. The agent with its plugin container is providing you with all the infrastructure to talk to the server, scheduling of metric gathering, scheduling of discovery etc. This means that you can fully concentrate on the business code of your plugin. RHQ just does the rest.

I have made the source code of those articles available as zip archive, that you can unpack in the modules/plugins/ directory.

I want to stop here for now. I will perhaps extend this on how to make the configuration flexible instead of hardcoding the server url. I also kept a third type of measurement data a secret, that I might be talking about some more.

Please provide feedback - either as comment to these articles or in the RHQ forums.

Here are again the references to the other articles in this series:


Part 1
Part 2
Part 4
Part 5








Technorati Tags:
, ,

1 comment:

raziyabhayani said...

I tried to follow the guidelines given by you to install the httptest plugin. I can see that its getting build. I see server starting along with the agent. However in the resources, plugin list http://127.0.0.1:7080/rhq/admin/plugin/plugin-list.xhtml

I cant see httptest listed. When I try to upload the plugin manually, it shoes done, but it still doesnt reflect it. what could be the possible issue? if you could please help.

Thanks.
Raziya