Thermostat/ExtendingCommandChannel

From IcedTea

Jump to: navigation, search

Thermostat Home

1 Programming for the Command Channel

Note: the following tutorial is about programming for Thermostat's command channel. The assumption is made that you've already figured out how to add a bundle to Thermostat, and that you know how to ensure that your bundle or bundles are loaded when needed, and that you know how to make sure that the relevant command channel bundles are loaded.

When developing for Thermostat, either on Thermostat's core or your own plugin, you will almost certainly at some point find a need for some user-initiated action to be taken by the agent. Given the decoupling of client from agent, you might be discouraged and assume that this will be some difficult or impossible task. However, you'd be wrong, as Thermostat includes a client-side API and agent-side extension point for you to implement custom client-initiated agent-side behaviour! We refer to this as the "command channel" informally.

It should be noted that direct client<=>agent communication, or command channel communication, should only be used for things where a client wants to trigger some action on the agent machine. One example for good use of command channel would be to terminate a Java process which is running on the host where an agent is running. DON'T use the command channel in order to get some information from a monitored JVM. The right way to solve this problem is to create an agent-side plug-in which pushes JVM info into storage which the client can then consume.

1.1 On the Client side

Let's first look at the client API. The entry point is the RequestQueue, which is provided as an OSGi service. Getting a reference to the queue is a one-liner:

   import com.redhat.thermostat.client.command.RequestQueue;
   ...
           RequestQueue queue = OSGIUtils.getInstance().getService(RequestQueue.class);
   ...

In order for this service to be available, the thermostat-client-command bundle must be loaded.

Now that you have a reference to the RequestQueue, you will need to create a Request. A request must specify a request type and a target, and almost certainly a receiver. The request type indicates what sort of response is expected, options include NO_RESPONSE_EXPECTED, RESPONSE_EXPECTED, and MULTIPART_RESPONSE_EXPECTED (Caution: multipart response is not yet implemented). If you indicated RESPONSE_EXPECTED, you should also implement RequestResponseListener and add the listener to the request. The target is a SocketAddress; this should match the configuration of the agent that this request is intended for. Plans are in the works to improve this API so that the host and port of the relevant agent is discovered automatically; in the meantime, you may look to the HostResolverHelper class as means of getting a valid SocketAddress from a given HostRef. You must set a receiver for the request; this is the class which will take action on the agent side when the request arrives. More on that below. You may wish to set one or more parameters on the request, to further tune the behaviour of the receiver. The meaning of any parameter set is completely dependent on a given receiver implementation.

Once you have prepared a request, you may queue it for dispatch. RequestQueue.putRequest() is a nonblocking call. If you specified a receiver, any response will be asynchronous. It should be noted that any requests which block the current thread until a response comes back MUST be scheduled and handled by a separate thread outside of the EDT.

A simple example:

   import com.redhat.thermostat.common.command.Request;
   import com.redhat.thermostat.client.command.RequestQueue;
   import com.redhat.thermostat.common.command.Request.RequestType;
   import com.redhat.thermostat.common.command.RequestResponseListener;
   ...
           RequestQueue queue = OSGIUtils.getInstance().getService(RequestQueue.class);
           // Assume you already have some access to a Storage implementation and a HostRef referring to the agent
           String address = storage.getConfigListenAddress(targetHostRef);
           String [] host = address.split(":");
           InetSocketAddress target = new InetSocketAddress(host[0], Integer.parseInt(host[1]));
           Request request = new Request(RequestType.RESPONSE_EXPECTED, target);
           request.setReceiver("com.redhat.thermostat.example.Receiver");
           request.setParameter("parameterName", "thisValue");
           request.addListener(new RequestResponseListener() {
               @Override
               public void fireComplete(Request request, Response response) {
                   if (response.getType() == ResponseType.OK) {
                       System.out.println("All is well.");
                   } else {
                       System.out.println("Did you forget to set the parameter on the request?");
                   }
               }
           });
           queue.putRequest(request);
   ...

1.2 On the Agent side

Great, now you know how to send a request! But what happens to the request after you send it? Well, we handle those details, so you don't have to think about it... up to a certain point. That point being, when the reqeust has arrived at the agent and it is time to respond to it! We've made another extension point for you, this time on the agent side of the house. The agent will look for an implementation of the RequestReceiver interface which has been registered as an OSGi service and matches the receiver specified in the request. We provide a convenience class for you to register your receiver implementation, all you need to do is ensure that your bundleactivator uses it. This would look like:

       @Override
       public void start(BundleContext context) throws Exception {
           receivers = new ReceiverRegistry(context);
           receivers.registerReceiver(new Receiver());
       }

A RequestReceiver implementation is your chance to execute custom code on the agent in response to client interaction. We've found two main reasons to want this, and suspect that most reasons you'd want it would fall into one of these categories:

  • Perform some one-time data collection task, such as creating a heap dump for a specific process.
  • Start or stop some ongoing data collection activity.

A RequestReceiver consumes a Request and produces a Response, which is delivered asynchronously back to the client (see listener discussion above). We would like to caution against using this response to communicate anything beyond the success/failure of the request itself. Specifically, please avoid trying to use this mechanism for sending monitoring data back to the client directly. The Thermostat agent and various clients are intentionally decoupled. This is an important aspect to the scalability of our architecture.

To fit with the examples above, here is an example implementation of a RequestReceiver:

   public class Receiver implements RequestReceiver {
       @Override
       public Response receive(Request request) {
           String someValue = request.getParameter("parameterName");
           Response response;
           if (someValue.equals("thisValue")) {
               // Do some stuff
               response = new Response(ResponseType.OK);
           } else {
               // Do other stuff
               response = new Response(ResponseType.NOK);
           }
           return response;
       }
   }

So that's that. It really is that simple! If you have any further questions, notice anything incorrect about this tutorial, or just want to tell us we're doing a great all-around job, please visit us on IRC (freenode/#thermostat) or subscribe and post to our mailing list.

Personal tools