Zoodiscovery

From Eclipsepedia

Jump to: navigation, search

Contents

What is ZooDiscovery?

ZooDiscovery is a discovery mechanism that runs as an OSGi service. It leverage Apache ZooKeeper robustness and implements Eclipse ECF Discovery API.(Hence the name!). ZooDiscovery is flexible and easy to configure.

This work is funded and made open by Remain Software and Industrial-TSI
Tinker.gif

Concepts

If you use OSGi remote services (See OSGi Compendium Specs chapter 13) you have to know the other end. In large installations this configuration can be quite cumbersome.

At Remain Software we develop software to manage nodes in a network.

We want our nodes to register themselves to us when they are in the network. This is fine if you manage a small office but not if you manage smart lightbulbs in a sky scraper.
Lightbubl.gif

As soon as the lightbulb is screwed into its socket, it can tap some power to activate its OSGi runtime. The runtime will activate the ILightBulb interface with methods dim(int), on() and off() as a remote service. Now, how do we get this service to interested parties...

ECF For the Win

The ECF discovery framework enabled us to create a Zookeeper based Discovery implementation. An addition to the already existing JmDNS (Zeroconf/Bonjour) and jSLP implementations.

An Apache Zookeeper server will replicate configuration data between other Zookeeper servers. The Zookeeper servers know each other and clients know one Zookeeper Server. So the smart bulb (which runs OSGi or did I mention that already?) is preconfigured with the address of its nearest Zookeeper server or gets this information dynamically by some kind of IP broadcast.

When the lightbulb publishes its Remote Service, ECF wakes up and publishes this service through the provided Discovery implementations. The Zookeeper discovery provider will immediately notify its nearest peers and the new lightbulb service is registered in all Zookeeper instances. When the Zookeeper instance that is connected to an interested party receives the data, the Discovery implementation will publish this service in its OSGi container.

The Lightbulb Control Center is waiting for the ILightBulb service and creates a UI in its console. The Building Maintainer can now control the lightbulb.

Zookeeperecf.gif

In the spirit of component based development: If you use ECF Discovery and you want the functionality that is provided by Zookeeper, you can replace your existing ECF implementation by this new one. Upgrade without pain.

Download

Downland both bundles: org.apache.zookeeper & org.eclipse.ecf.provider.zookeeper
from CVS server at http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.ecf/?root=RT_Project
Anonymous CVS info:  :pserver:anonymous@dev.eclipse.org:/cvsroot/rt

If you use Eclipse, just open the CVS Repositories perspective and add a new CVS repository by pressing the button in the view toolbar. Then copy everything between quotes here ":pserver:anonymous@dev.eclipse.org:/cvsroot/rt" and press paste (CTRL+V) in the wizard.

You can find the apache zookeeper bundle in the org.eclipse.ecf/protocols/bundles directory You can find the discovery provider bundle in the org.eclipse.ecf/providers/bundles directory

Inner Concepts

ZooDiscovery implements both ECF discovery interfaces: IDiscoveryAdvertiser and IDiscoveryLocator. That is, ZooDiscovery can publish our services and gets us noticed about discovered services. Perfect! But how? A ZooDiscovery instance running at your machine does its job by exchanging data with other ZooDiscovery instance(s) running elsewhere. So each running ZooDiscovery service must know where that other "elsewhere" exactly is. This is why we should first make our ZooDiscovery happy, giving it an IP address to play with.
To keep it smooth, let's take it step by step following these cases:

How to configure ZooDiscovery container?

To tell ZooDiscovery where to look for new services, we use one of these three configuration properties: "zoodiscovery.flavor.standalone", "zoodiscovery.flavor.centralized" or "zoodiscovery.flavor.replicated". Whichever property you use, the ZooDiscovery container is configured the same way. Moreover, ZooDiscovery keeps trying re/connecting automatically.

Note: IP addresses used throughout this wiki are for illustrative purpose, so needless to say you should substitute them with your own IP addresses.

Standalone ZooDiscovery: "zoodiscovery.flavor.standalone"

Let's start with "zoodiscovery.flavor.standalone" for the following illustration. This property accepts as value one or more IP addresses of target machines we want to connect to.

For example, suppose we have 4 ZooDiscovery instances z0, z1, z2 and z3:
z0 instance running on locallhost resolving to IP address "192.1.10.9"
z1 instance running on machine with IP address "192.1.10.10"
z2 instance running on machine with IP address "192.1.33.33"
z3 instance running on machine with IP address "192.1.34.44"

and some arbitrary scenario's: We (z0) want:
Scenario 1 - to discover services advertised by z1, z0 should, then, talk to z1 instance and our property would be set this way: "zoodiscovery.flavor.standalone=192.1.10.10".
Scenario 2 - to discover services advertised by z1 and z2, we set our property so: "zoodiscovery.flavor.standalone=192.1.10.10 , 192.1.33.33".
Scenario 3 - to discover services advertised by z1,z2 and z3, our property is now set so: "zoodiscovery.flavor.standalone=192.1.10.10 , 192.1.33.33, 192.1.34.44".

Note the comma used to separate the list of the IP's.

In code this would look like this:

IContainer z0= null;
try { 
//"ecf.discovery.zookeeper" is the container name we want to initiate.
container = ContainerFactory.getDefault().createContainer("ecf.discovery.zoodiscovery");
} catch(ContainerCreateException e1){ // TODO 
}
// then one of the senario's 
//Scenario 1.  We build an ECF ID  to use it to connect with ZooDiscovery  instance z1 running at 192.1.10.10.
ID target = container.getConnectNamespace().createInstance(  
//In "zoodiscovery.flavor.standalone" we don't set our own IP address. Only the target machine(s) address(es) we want to connect to, are set.
new String[] { "zoodiscovery.flavor.standalone=192.1.10.10" }); 
//Or scenario 2. We build an ECF ID to use it to connect with 2 ZooDiscovery  instances,  z1 running at 192.1.10.10 and z2 at 192.1.33.33.
ID target = container.getConnectNamespace().createInstance(  
//In "zoodiscovery.flavor.standalone" we don't set our own IP address. Only the target machine(s) address(es) we want to connect to, are set.
new String[] { "zoodiscovery.flavor.standalone=192.1.10.10, 192.1.33.33" });
//Or Scenario 3. We build an ECF ID  to use it to connect with 3 ZooDiscovery  instances z1,z2 and z3  running at 192.1.10.10, 192.1.33.33 and 192.1.34.44, respectively.
ID target = container.getConnectNamespace().createInstance(  
//In "zoodiscovery.flavor.standalone" we don't set our own IP address. Only the target machine(s) address(es) we want to connect to, are set.
new String[] { "zoodiscovery.flavor.standalone=192.1.10.10, 192.1.33.33, 192.1.34.44" });
// then connect
z0.connect(target, null);
//Note: After calling connect(target, null) successfully (no exception!), ZooDiscovery takes it over from here and will keep trying connecting (reconnecting in case of lost connections) automatically.


// To advertise services we need adapting our container this way:
IDiscoveryAdvertiser discoveryAdvertiser = (IDiscoveryAdvertiser) z0.getAdapter(IDiscoveryAdvertiser.class);
//then we enjoy calling IDiscoveryAdvertiser contract methods
// To localize/discover services we need adapting it this way:
IDiscoveryLocator discoveryLocator = (IDiscoveryLocator) z0.getAdapter(IDiscoveryLocator.class);
//then we enjoy calling IDiscoveryLocator  contract methods.

So far, so good. But what does standalone stand for? The answer lies in another hint question: can z1,z2 or z3 discover services published by z0 with the same configuration code above. The answer is: Sorry folks! No. For example, if we wanted z1 to discover what z0 is pubishing as well, then we should've made z1 (on machine 192.1.10.10) connect to z0 :

//ZooDiscovery instance z1 on machine 192.1.10.10 that will discover  services published by z1 (on machine 192.1.10.9):
IContainer z1= null;
try { 
//"ecf.discovery.zookeeper" is the container name we want to initiate.
container = ContainerFactory.getDefault().createContainer("ecf.discovery.zoodiscovery");
} catch(ContainerCreateException e1){ // TODO 
} 
//We build an ECF ID  to use it to connect with a ZooDiscovery instance z0 running at 192.1.10.9.
ID target = container.getConnectNamespace().createInstance(  
//In "zoodiscovery.flavor.standalone" we don't set our own IP address. Only the target machine(s) address(es) we want to connect to, are set.
new String[] { "zoodiscovery.flavor.standalone=192.1.10.9" }); 
// then try connecting. 
z1.connect(target, null);  

// To advertise services we need adapting our container this way:
IDiscoveryAdvertiser discoveryAdvertiser = (IDiscoveryAdvertiser) z1.getAdapter(IDiscoveryAdvertiser.class);
//then we enjoy calling IDiscoveryAdvertiser contract methods
// To localize/discover services we need adapting it this way:
IDiscoveryLocator discoveryLocator = (IDiscoveryLocator) z1.getAdapter(IDiscoveryLocator.class);
//then we enjoy calling IDiscoveryLocator  contract methods.

So, the configuration property "zoodiscovery.flavor.standalone=ipaddress1, ipaddress2,ipaddress4,ipaddress5,..." instructs our (being initiated) ZooDiscovery to connect to machines with IP addresses ipaddress1, ipaddress2, ipaddress4,ipaddress5, and discover services published by ZooDiscovery instances running on each one. Should they need to discover our services as well, then they must connect to us by including our IP address when configuring and initiating their ZooDiscovery instances. So each ZooDiscovery stands independent from other ZooDiscovery instances.

Centralized ZooDiscovery: "zoodiscovery.flavor.centralized"

In contrast to property "zoodiscovery.flavor.standalone", property "zoodiscovery.flavor.centralized" expects exactly one IP address, the one of ZooDiscovery instance that will play the central role. All of ZooDiscovery instances configured with this property publish to one point (ZooDiscovery running at that IP address) and discover services known at that central point. That is, you need to connect just to one central ZooDiscovery and get all services published by all other members.

let's assume z1 instance (running on machine with IP address "192.1.10.10") plays this central role.

// We configure central ZooDiscovery  container z1 (on machine with IP address "192.1.10.10")  with its own IP address:
//...code as above 
//referring to itself
ID target = container.getConnectNamespace().createInstance(  
new String[] { "zoodiscovery.flavor.centralized=192.1.10.10" });
//...code as above.
// We configure ZooDiscovery   container z0 (on machine  "192.1.10.9") to talk to the center z1: 
ID target = container.getConnectNamespace().createInstance(  
new String[] { "zoodiscovery.flavor.centralized=192.1.10.10" });
// We configure ZooDiscovery   container z2 (on machine  "192.1.33.33") to talk to the center z1 : 
ID target = container.getConnectNamespace().createInstance(  
new String[] { "zoodiscovery.flavor.centralized=192.1.10.10" });
// We configure ZooDiscovery   container z3 (on machine  "192.1.34.44") to talk to the center z1 :
ID target = container.getConnectNamespace().createInstance(  
new String[] { "zoodiscovery.flavor.centralized=192.1.10.10" });

As you can see, just one IP address is used to configure all other memebers (ZooDiscovery instances). The IP address of the central ZooDiscovery which all participating instances publish their services to, and discover from. It differs with the stand alone property in that you connect to one point and get services published by all participating ZooDiscovery instances, but if the central ZooDiscovery (in our example the one runnning at 192.1.10.10) is down, the whole orbiting ZooDiscovery members are doomed down. They all depend on one center, shut down the center and every other instance is headless. Bring the center up, and every other member is happily live and kicking. Additionally, this property expects exactly one IP address as value.

Replicated ZooDiscovery: "zoodiscovery.flavor.replicated"

...being edited

How to build a ServiceInfo object and publish it

//Some  location
URI uri = URI.create("http://www.example.com");
//Some service priority
int priority = 0;
//Some service weight
int weight = 3;
//Some random service properties
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setProperty("foobar", new String("foobar"));
serviceProperties.setPropertyBytes("foobar1", new byte[] { 1, 2, 3 });
IServiceTypeID serviceTypeID = null;
try {
serviceTypeID = ServiceIDFactory.getDefault().createServiceTypeID(
DiscoveryContainer.getSingleton().getConnectNamespace(),new String[] {"service1","service2"}, new String[] {"someProtocol"});
} catch (IDCreateException e) {//TODO			
}
//build a service info instance to be published
ServiceInfo  serviceInfo = new ServiceInfo(uri, "myServiceName", serviceTypeID, priority, weight, serviceProperties);
//advertise the service
discoveryAdvertiser.registerService(serviceInfo);

Tip: If you already have an OSGi ServiceReference of the service in hand, it might be handy to build a ServiceInfo instance this way:

//Using AdvertisedService class means creating a compile-time dependency on "org.eclipse.ecf.provider.zookeeper.core". So bear in mind when using it.
IServiceInfo advertised = new AdvertisedService(myServiceReference);
discoveryAdvertiser.registerService(serviceInfo);

I want to get notified about discovered services

You get notified about discovered services by registering yourself as an IServiceListener. Let's take it for a ride and make an inner listener class to see how lightweight its contract is:

IServiceListener sl = new IServiceListener() {
public void serviceUndiscovered(IServiceEvent anEvent) {
// service lost, do something..
}
public void serviceDiscovered(IServiceEvent anEvent) {
// new service is in, do something..
}
}; 
//register to get informed
discoveryLocator.addServiceListener(sl);
//To register for service discoveries with a specific type, you might add the type as well using this method:
addServiceListener(IServiceTypeID aType, IServiceListener aListener), instead.
//To register for service types discoveries, you might consider registering as IServiceTypeListener

Note: ZooDiscovery tracks OSGi services being registered under IServiceListener or IServiceTypeListener, and add them as listeners so that (if you choose to) you don't have to add them explicitly the way we did just above. This is handy when your design is a bit more dynamic/component driven.

Advanced configuration

Fine tuning the underlying ZooKeeper

being edited...