Java Dynamic Management Kit 5.1 Tutorial

Chapter 15 Discovery Service

The discovery service enables you to discover Java dynamic management agents in a network. This service relies on a discovery client object that sends out multicast requests to find agents. In order to be discovered, an agent must have a registered discovery responder in its MBean server. Applications can also use a discovery monitor that detects when discovery responders are started or stopped.

The combination of these functions enables interested applications to establish a list of active agents and keep it up to date. In addition to knowing about the existence of an agent, the discovery service provides the version information from an MBean server's delegate and the list of communication MBeans that are currently registered. The discovery service uses the term communicators to designate a set of MBeans consisting of protocol adaptors and connector server objects.

Often, the discovery client and the discovery monitor are located in a manager application that wants to know about the available agents. However, agent applications are free to use the discovery service because they might require such information for cascading (see “Cascading Agents”) or for any other reason. To simplify using the discovery service in an agent, all of its components are implemented as MBeans.

The code samples in this topic are found in the current/Discovery directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

15.1 Active Discovery

In active discovery, the discovery client initiates searches for agents on the network. It involves a discovery client that sends the discovery request and a discovery responder in each agent that responds. Each instance of the responder supplies the return information about the agent in which it is registered. The return information is represented in the discovery client by a vector of discovery response objects.

The application containing the discovery client can initiate a search at any time. For example, it might do a search when it is first started and periodically search again for information about the communicators that might have changed. For each search, the discovery client broadcasts a request and waits for the return information from any responders.

In the following sections, we describe each of these objects in further detail.

15.1.1 Discovery Client

The DiscoveryClient class provides methods to discover agents. The active discovery operation sends a discovery request to a multicast group and waits for responses. These messages are proprietary and are not exposed to the user. Discovery clients can only discover agents listening on the same multicast group and port, so your design must coordinate this information between the discovery client and responders.

You can instantiate and perform searches from multiple discovery clients in a single application. Each discovery client can be configured to use different multicast groups or ports, enabling you to discover different groups of agents.

The discovery services enable users to specify a local interface from which to send out multicast messages. This is useful when working on a system that has multihome interfaces connecting to disconnected multinetworks. In addition, the DiscoveryResponder constructor enables you to specify the host address to be used to build the discovery response. The address can be specified either as an IPV4 or IPv6 address, or as a host name.


Example 15–1 Instantiating and Initializing a Discovery Client

public class Client {
    public static void main(String[] args) throws Exception {
	       BufferedReader br
	           = new BufferedReader(new InputStreamReader(System.in));
 
	       discoveryClient = new DiscoveryClient();
	       discoveryClient.start();

	       while(true) {
	          echo("\n>>> Press return to discover connector servers;");
	          echo(">>> Type a host name then return to discover connector 
                servers running on that machine;");
	          echo(">>> Type bye then return to stop the client.");

    	    String s = br.readLine();

	          if (s.equalsIgnoreCase("bye")) {
		           System.exit(0);
	          } else {
		           try {
		                discover(s);
		           } catch (Exception e) {
		                echo("Got exception "+e);
		                e.printStackTrace();
		           }
	          }
	       }
    }

Once you have created the discovery client, before initiating searches, you must call the discovery client's start method, as shown in Example 15–1. This will create its multicast socket and join the multicast group used for broadcasting its discovery request. The default multicast group is 224.224.224.224 and the default port is 9000. These can be set to other values through the multicastGroup and multicastPort attributes, but only when the state of the discovery client is OFFLINE.

The scope of the discovery request depends on the time-to-live used by the multicast socket. Time-to-live is defined by the Java class java.net.MulticastSocket to be a number between 1 and 255. By default, the time-to-live is 1, which corresponds to the host's local area network. You can modify this value at any time by setting the discovery client's TimeToLive attribute.

By default, a discovery client waits for responses for one second after it has sent a discovery request. This period can be customized by setting a value in milliseconds for its TimeOut attribute. When setting this attribute, you should take into account estimated time for a round-trip of a network packet using the given time-to-live.

Once started, the discovery client in the example above awaits user inputs before starting searches.

15.1.2 Performing a Discovery Operation

An application triggers a search operation by invoking the findMBeanServers or findCommunicators methods on an active DiscoveryClient object. Using the current settings, it will send the multicast request and block for the timeout period. At the end of the timeout period, these methods return the responses that were received.

Both methods return a vector of DiscoveryResponse objects. This class exposes methods for retrieving information about the MBean server and the registered communicator MBeans in the agent. The MBean server information is the same as that exposed by that agent's MBean server delegate. The communicators are identified by ConnectorAddress objects and indexed by object name in a hash table.

Both search methods return the information about the agent's MBean server. The hash table of communicator MBeans is always empty for discovery responses returned by the findMBeanServers method. Otherwise, you can extract object names and protocol information from the hash table. One way of distinguishing the communicator MBeans is to rely on the default names provided by the ServiceName class.


Note –

All discovery messages sent between components of the discovery service are compatible between applications running different versions of the Java platform or between versions 5.0 and 5.1 of the Java DMK. However, these different configurations are not compatible for subsequent management operations through connectors. You can use the getImplementationVersion method of the DiscoveryResponse object to determine both the Java platform and product version numbers.


In our example, we request all information about the agents and print out all information in the discovery responses.


Example 15–2 Performing a Discovery Operation

private static void discover(String host) throws Exception {
	   Vector v = null;
	   if (host ==  null || host.equals("")) {
	      v = discoveryClient.findCommunicators();
	   } else {
	      v= discoveryClient.findCommunicators(host);
	   }

	   if (v.size() == 0) {
	      echo("No connector server has been found.");

	      return;
	   }

	   for (int i=0; i<v.size(); i++) {
	        DiscoveryResponse dr = (DiscoveryResponse)v.get(i);
	        JMXServiceURL url = null;

	        // legacy servers
	        Collection c = dr.getObjectList().values();
	        for (Iterator iter=c.iterator(); iter.hasNext();) {
		         Object o = iter.next();

		         if (!(o instanceof ConnectorAddress)) {
		             continue;
		         }

		         ConnectorAddress ca = (ConnectorAddress)o;
		         if (ca.getConnectorType().equals("SUN RMI")) {
		             url = new JMXServiceURL("jdmk-rmi",
					                           ((RmiConnectorAddress)ca).getHost(),
					                           ((RmiConnectorAddress)ca).getPort());
		        // Repeat for jdmk-http and jdmk-https connectors 
           [...]
 	        } else {
		            echo("Got an unknown protocol: "+ca.getConnectorType());

		            continue;
		        }

		        echo("\nFound a legacy server which is registered 
                  as a legacy MBean: "
		              +url.getProtocol()+" 
                 "+url.getHost()+" 
                 "+url.getPort());
		        echo("Connecting to that server.");
		        JMXConnector jc = JMXConnectorFactory.connect(url);
		        echo("Its default domain is 
                 "+jc.getMBeanServerConnection().getDefaultDomain());
		        echo("Closing the connection to that server.");
		        jc.close();
	       }

	       // JMX-remote servers
	       JMXServiceURL[] urls = dr.getServerAddresses();

	       echo("");
		    for (int ii=0; ii<urls.length; ii++) {
		      echo("\nFound a server which is registered 
                as a JMXConnectorServerMBean: "
		            +urls[ii]);
		      echo("Connecting to that server.");
		      JMXConnector jc = JMXConnectorFactory.connect(urls[ii]);
		      echo("Its default domain is 
              "+jc.getMBeanServerConnection().getDefaultDomain());
		      echo("Closing the connection to that server.");
		      jc.close();
	       }
	    }
}

On the agent side, the discovery responder automatically replies to discovery requests. Any active, registered responder in the same multicast group that is reached within the given time-to-live of the request will respond. It will automatically gather the requested information about its MBean server and send the response. The settings of the responder do not affect its automatic reply to discovery requests. In 15.2.1 Discovery Responder we will cover how its settings control passive discovery.

The discovery client can search for agents via all the connector protocols supported by Java DMK, both current and legacy. Java DMK 5.1 introduces a new DiscoveryResponse method, getServerAddresses, which is used to get the addresses of any servers registered in an MBean server as a JMXConnectorServerMBean. The getServerAddresses method can discover servers that are either instances of the JMX Remote API JMXConnectorServer, or legacy servers wrapped to appear as such. This new method ensures compatibility between the following pairs of discovery clients and servers created using versions 5.0 and 5.1 of Java DMK.

All the above relations between versions are also true for the passive discovery monitor.

In active discovery, the discovery client controls all parameters of a search it initiates, including the response mode of the discovery responder. The discovery client determines whether responses are sent back on a different socket (unicast) or sent to the same multicast group. The default is unicast: if you want to use the multicast response mode, set the PointToPointResponse attribute to false before initiating the discovery.

15.1.2.1 Unicast Response Mode

When the PointToPointResponse boolean attribute is true, the discovery client specifies unicast mode in its discovery requests. The responder will create a datagram socket for sending the response only to the discovery client. As shown in the following diagram, each responder will send its response directly back to the discovery client. The datagram socket used by each responder is bound to its local host address; this cannot be customized.

Figure 15–1 Unicast Response Mode

Unicast response mode

15.1.2.2 Multicast Response Mode

When the PointToPointResponse boolean attribute is false, the discovery client specifies multicast mode in its requests. The discovery responder will use the existing multicast socket to send response, broadcasting it to the same multicast group as the request. As shown in the following diagram, every member of the multicast group will receive the message, but only the discovery client can make use of its contents. Multicast mode avoids having to open another socket for the response, but all of the responses will create traffic in each application's socket.

Figure 15–2 Multicast Response Mode

Multicast response mode

15.2 Passive Discovery

In passive discovery, the entity seeking knowledge about agents listens for the activation or deactivation of their discovery responders. When discovery responders are started or stopped, they send out a proprietary message that contains all discovery response information. The DiscoveryMonitor object waits to receive any of these messages from the multicast group.

A discovery monitor is often associated with a discovery client. By relying on the information from both, you can keep an up-to-date list of all agents in a given multicast group.

Figure 15–3 Passive Discovery of Discovery Responders

Diagram showing passive discovery of discovery responders

Therefore, configuring and starting the discovery responder is an important step to the overall discovery strategy of your applications.

15.2.1 Discovery Responder

The agents that are configured to be discovered must have an active DiscoveryResponder registered in their MBean server. The responder plays a role in both active and passive discovery:

Both types of messages are proprietary and their contents are not exposed to the user. These messages contain information about the MBean server, its delegate's information and a list of communicator MBeans, unless not requested by the discovery client.

In our example we create the discovery responder in the MBean server and then activate it. Then, we create different connector servers that will discover the agent passively, due to its active discovery responder.


Example 15–3 Creating a Discovery Responder

public class Responder {
    public static void main(String[] args) {
	      try {
	          MBeanServer myMBeanServer = 
                MBeanServerFactory.createMBeanServer();

	          echo("\nCreate and register a DiscoveryResponder MBean.");
	          ObjectName dc = 
               new ObjectName("DiscoveryExample:name=DiscoveryResponder");
          myMBeanServer.createMBean("com.sun.jdmk.discovery.DiscoveryResponder", 
                                    dc);
	          myMBeanServer.invoke(dc, "start", null, null);
	    
	          // Create an HtmlAdaptorServer on the default port.
	          [...]    
	
          // Create JMX Remote API connector servers
	    	    JMXServiceURL url;
	          JMXConnectorServer server;
	          ObjectName on;
	    
	          // rmi
	          url = new JMXServiceURL("rmi", null, 0);
	          server = 
             JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                            null, 
                                                            myMBeanServer);
	          server.start();
	          url = server.getAddress();
		    
     	    on = new ObjectName("jmx-remote:protocol=rmi");
	          myMBeanServer.registerMBean(server, on);
    	    
          // Create RMI/IIOP connector server
          [...]

          // Create JMXMP connector server
          [...]

          // stop/start the responder to send a notification
	          myMBeanServer.invoke(dc, "stop", null, null);
           Thread.sleep(100);
	          myMBeanServer.invoke(dc, "start", null, null);

          // Create wrapped legacy RMI and HTTP connector servers 
          [...]
	    
	          // Repeat for the other current and legacy connector protocols
          [...]

	          // stop/start the responder to allow a Monitor to find them
	          myMBeanServer.invoke(dc, "stop", null, null);
           Thread.sleep(100);
	          myMBeanServer.invoke(dc, "start", null, null);
           
           [...]

	          echo("All servers have been registered.");

	          echo("\n>>> Press return to exit.");
	          System.in.read();
	          System.exit(0);
	      } catch (Exception e) {
	          e.printStackTrace();
	   }
}

The discovery responder has attributes for exposing a multicast group and a multicast port. These attributes define a multicast socket that the responder will use to receive discovery requests. It will also send activation and deactivation messages to this multicast group. When sending automatic responses to discovery requests, the time-to-live is provided by the discovery client. The responder's time-to-live attribute is only used when sending activation and deactivation messages.

We use the default settings of the discovery responder that are the multicast group 224.224.224.224 on port 9000 with time-to-live of 1. In order to modify these values, you need to set the corresponding attributes before starting the discovery responder MBean. You can also specify them in the class constructor. If the responder is active, you will need to stop it before trying to set any of these attributes. In that way, it will send a deactivation message using the old values and then an activation message with the new values.

15.2.2 Discovery Monitor

The discovery monitor is a notification broadcaster: when it receives an activation or deactivation message from a discovery responder, it sends a discovery responder notification to its listeners. Once its parameters are configured and the monitor is activated, the discovery is completely passive. You can add or remove listeners at any time.

The DiscoveryMonitor MBean has multicast group and multicast port attributes that determine the multicast socket where it will receive responder messages. Like the other components of the discovery service, the default multicast group is 224.224.224.224 and the default port is 9000. You can specify other values for the group and port either in the constructor or through attribute setters when the monitor is off-line.

Example 15–4 shows a discovery monitor being started, and then a listener being added.


Example 15–4 Instantiating and Starting a Discovery Monitor

public class Monitor {
    public static void main(String[] args) throws Exception {

       echo("Create a DiscoveryMonitor.");
	      DiscoveryMonitor dm = new DiscoveryMonitor();
	      dm.start();

	      echo("Add a listener to receive monitor notifications.");
	      dm.addNotificationListener(new MyListener(), null, null);
 
	      echo("Waiting for new server notifications ...");

	      echo("\nType any key to stop the monitor.");
	      System.in.read();

	      dm.stop();
	      echo("\nThe monitor has been stopped.");

	      System.exit(0);
    }

The discovery monitor must be activated with the start operation before it will receive responder messages and send notifications. If it is being used as an MBean, it will be stopped automatically if it is unregistered from its MBean server. If it is not used as an MBean, as is the case here, you should invoke its stop method before your application exits.

15.2.3 Discovery Responder Notifications

When it receives a responder's activation or deactivation message, the discovery monitor sends notification objects of the DiscoveryResponderNotification class. This notification contains the new state of the discovery responder (ONLINE or OFFLINE) and a DiscoveryResponse object with information from the agent where the responder is located.

The listener could use this information to update a list of agents in the network. In our example, the listener is the agent application itself, and the handler method only prints out the information in the notification.


Example 15–5 Discovery Responder Notification Handler

private static class MyListener implements NotificationListener {
	   public void handleNotification(Notification notif, Object handback) {

	      try {
       		DiscoveryResponderNotification dn 
                               = (DiscoveryResponderNotification)notif;
            DiscoveryResponse dr = (DiscoveryResponse)dn.getEventInfo()  ;

		         JMXServiceURL url = null;

		         // legacy servers
		         Collection c = dr.getObjectList().values();
		         for (Iterator iter=c.iterator(); iter.hasNext();) {
		              Object o = iter.next();
		    
		              if (!(o instanceof ConnectorAddress)) {
			               continue;
		              }
		    
		              ConnectorAddress ca = (ConnectorAddress)o;
		              if (ca.getConnectorType().equals("SUN RMI")) {
			               url = new JMXServiceURL("jdmk-rmi",
                             ((RmiConnectorAddress)ca).getHost(),
                             ((RmiConnectorAddress)ca).getPort());
		              // Repeat for jdmk-http and jdmk-https connectors
		              [...]

                  } else {
			 			       continue;
		              }

                  echo("\nFound a legacy server which is registered as
                       a legacy MBean: "
                       +url.getProtocol()+" "+url.getHost()+" "
                       +url.getPort());
		              echo("Connecting to that server.");
                  JMXConnector jc = JMXConnectorFactory.connect(url);
		      		   jc.close();
		         }

		         // JMX Remote API servers
		         JMXServiceURL[] urls = dr.getServerAddresses();
		
		         echo("");
		         for (int ii=0; ii<urls.length; ii++) {
		              echo("\nFound a server which is registered as a 
                      JMXConnectorServerMBean: "
			                +urls[ii]);
		              echo("Connecting to that server.");
		              JMXConnector jc = JMXConnectorFactory.connect(urls[ii]);
		              echo("Its default domain is 
                       "+jc.getMBeanServerConnection().getDefaultDomain());
		      	      jc.close();
		         }
	        } catch (Exception e) {
		         echo("Got unexpected exception: "+e);
		         e.printStackTrace();
	        }
      }
    }

15.3 Running the Discovery Example

The examplesDir/current/Discovery directory contains the source file for the application that demonstrates the discovery service.

To Run the Discovery Example
  1. Compile the Java classes.


    $ javac -classpath classpath *.java
    
  2. Start the Client.


    $ java -classpath classpath Client
    

    You will be prompted to press Enter to discover connector servers, to provide the host name of a machine where servers are running or to exit the discovery component. However, there are not yet any servers to discover, because the Responder has not yet been started.

  3. In a second terminal window, start the Monitor


    $ java -classpath classpath Monitor
    

    You will see confirmation that the monitor has started, and that it is passively listening for notifications,

  4. In a third terminal window, start the Responder.


    $ java -classpath classpath Responder
    

    You will see the creation of the different connector servers. Then you will be invited to create further connector servers by pressing Enter.

    In the window in which you started the Monitor, you will see the connector servers created by Responder being discovered.

  5. Press Enter in the terminal window in which you started the Client.

    You will see the Client discovering the connector servers created by the Responder, and connecting to them to retrieve information about them, before closing the connections.

    You can continue discovering and connecting to connector servers for as long as the Responder is active.

  6. When you have finished, stop the Responder, the Monitor and the Client.