Tuesday, April 24, 2018

OSGi R7 Highlights: Cluster Information Specification

In my experience, one of the most powerful parts of the OSGi specification are the Remote Services and Remote Service Admin, which enable you to develop complex, distributed applications in a modular way. However, in order to deploy such a distributed application, it is key to find out on which devices bundles can be deployed. In previous releases, OSGi specifications for remote management are already available for certain domains (i.e. telecommunications with TR-069 Connector Service) or protocols (i.e., REST Management Service).

With OSGi R7, we now introduce the Cluster Information specification, a protocol-agnostic specification to discover, list and inspect the available devices in a distributed compute environment, and to provision these devices with OSGi bundles.

A Cluster of Nodes

At the heart of the cluster information specification, is the NodeStatus service, which is used to indicate the presence of a node within the cluster. This could be any entity, such as a virtual machine, a docker container, a physical machine, a database, your Raspberry Pi, etc. Each node has a globally unique identifier, a name of the cluster it belongs to, and optionally a number of other properties such as the endpoint at which the node can be accessed, the physical location where this node is located or some application specific tags. These properties are available as service properties on the Node Status service. For a full list of properties, take a look at the specification.

A special case of node is, of course, an OSGi framework, in which case we use the FrameworkNodeStatus as presence service, which extends NodeStatus. In addition to the Node Status service properties, this one also provides some OSGi-specific properties, such as the framework version, Java version, etc. Also, when an OSGi framework is part of a cluster, this means it gets access to remote services of any other OSGi framework in that cluster. Ensuring the discovery, visibility and access of remote services within the cluster is the responsibility of the Remote Service Admin.

As we all know service properties are quite cumbersome to parse and process. Therefore, we also provide DTO types NodeStatusDTO and FrameworkNodeStatusDTO to have type-safe access to these properties. Converting service properties to these DTO types can be easily done using the Converter specification. For example, the following component lists each node in the cluster with name mycluster and prints out its id.

@Component
public class MyClusterManagement {
    private static final Converter CONVERTER
                        = Converters.standardConverter();

    @Reference(cardinality=MULTIPLE, 
               policy=DYNAMIC,
               target="(osgi.clusterinfo.cluster=mycluster)")
    void addNode(NodeStatus ns, Map props) {
        // Convert properties to the DTO for type safe access
        NodeStatusDTO dto = CONVERTER.convert(props)
                                     .to(NodeStatusDTO.class);
        // Print out the node id
        System.out.println("Node added: "+dto.id);
    }
}

For more info on using the Converter, take a look at David Boschaert's blog post.

Provisioning Nodes

The Framework Node Status service also provides a way to interact with the OSGi framework by also extending the FrameworkManager interface. This provides a similar interface as the REST Management service, but accessible as an OSGi Remote service.

Now we can extend the previous example and write a provisioner that deploys some location specific bundles.

@Component
public class FrameworkProvisioner {
    private static final Converter CONVERTER
                        = Converters.standardConverter();

    @Reference(cardinality=MULTIPLE,
               policy=DYNAMIC)
    void addFramework(FrameworkNodeStatus fns, Map props) {
        // Convert properties to the DTO for type safe access
        NodeStatusDTO dto = CONVERTER.convert(props)
                                     .to(NodeStatusDTO.class);

        // Check the ISO 3166-1 alpha 3 country code
        if ("DEU".equals(dto.country)) {
            // If this framework runs in Germany
            // install a special bundle into it
            try {
                fns.installBundle("Germany specific bundle");
            } catch (Exception e) {
                // log
            }
        }
    }
}

Metrics

Besides announcing static properties about a node by means of the service properties, it is also possible for a node to give access to dynamically changing properties, which we call metrics. To that end, the NodeStatus service provides the getMetrics() method, which returns a map with the current metric values. Which metrics are actually provided depends on the type of node and is implementation specific. For example, a NodeStatus of a VM could return usage metrics of that VM given by your cloud provider.

Again, an implementation can also provide DTO types to convert this map to a type-safe object. For example, the Eclipse Concierge reference implementation provides a JMXMetricsDTO that exposes JMX metrics of the JVM.

// From service registry
NodeStatus ns = ...;
// Obtain all metrics for this node
Map metrics = ns.getMetrics();

// Convert the metrics map to a DTO for type-safe access
JMXMetricsDTO dto = Converters.standardConverter()
                              .convert(metrics)
                              .to(JMXMetricsDTO.class);

// Use metrics
System.out.println("System Load Average: " 
                   + dto.systemLoadAverage);

Case Study: OSGi Community Event Trains Demo

Those of you who attended the OSGi Community Event the last couple of years will most likely have seen the trains demo. This demo uses a number of different OSGi open source projects, and commercial products from OSGi Alliance members to manage and control the operation of LEGO® trains.
All the trains, signals, track switches, train control buttons, notification screens etc. are controlled by different Raspberry Pi devices, adding up to a total of 8 different devices running an OSGi framework that have to be provisioned with different bundles depending on their role in the demo. This makes it a very tedious setup to get all devices up and running correctly.

For this use case, we can use the osgi.clusterinfo.tags property of the Node Status service to associate application specific tags to each OSGi framework node, indicating the roles it has to fulfill (i.e., "switchcontroller","traincontroller","signalcontroller", ...). We can then use these tags in a provisioning component similar to the example above, installing the correct bundles depending on the roles.

These tags can be used in a very versatile way, and in fact, any bundle in the OSGi framework can add a custom tag to the Node Status service representing this framework. To do so, any bundle can register a service with the org.osgi.service.clusterinfo.tags property set, which will automatically be added to the Node Status service tags.

The Cluster Information specification can also be used to manage non-OSGi entities. For example, in the demo, we also used an MQTT broker to connect with various external sensors. Again, this resulted in configuring on each of the devices the correct URI for connecting to the broker. By using a Node Status service that announces the presence of the MQTT broker, we have a single point of configuration in the cluster, and any component can depend on the presence of the broker by adding the following reference.
@Reference(target="(osgi.clusterinfo.tags=mqttbroker)")
void setBroker(NodeStatus ns, Map props) {
   // Convert properties to the DTO for type safe access
   NodeStatusDTO dto = CONVERTER.convert(props)
                                .to(NodeStatusDTO.class);
   // Now connect to the broker given the endpoint
   connect(dto.endpoint);
}

Notice again how we can use a custom tag to indicate this is an MQTT broker.

Want to know more?

This blog post gave a quick overview on the Cluster Information specification and some potential use cases. For more detailed information, see the spec chapter. If you want to play around with some actual code, the reference implementation is available in the Eclipse Concierge repository, with an actual release to follow soon.


Want to find out more about OSGi R7?

This is one post in a series of 12 focused on OSGi R7 Highlights.  Previous posts are:
  1. Proposed Final Draft Now Available
  2. Java 9 Support
  3. Declarative Services
  4. The JAX-RS Whiteboard
  5. The Converter
Be sure to follow us on Twitter or LinkedIn or subscribe to our Blog feed to find out when it's available.

No comments:

Post a Comment