Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Zoodiscovery
Contents
- 1 Zoodiscovery/FAQ
- 2 What is ZooDiscovery?
- 3 Concepts
- 4 Building and Running a dedicated Zookeeper server
- 5 Download OSGi bundles
- 6 Download Source
- 7 Inner Concepts
- 8 Advanced configuration
Zoodiscovery/FAQ
What is ZooDiscovery?
ZooDiscovery is a discovery mechanism that runs as an OSGi service. It leverages Apache ZooKeeper robustness and implements Eclipse ECF Discovery API.(Hence the name!). ZooDiscovery is flexible and easy to configure.
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.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.
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.
Building and Running a dedicated Zookeeper server
Building a Zookeeper based discovery server is already done on GitHub here: https://github.com/ECF/ZooServer
Download OSGi bundles
OSGi bundles can be downloaded from our ECF download site. Instructions are here https://www.eclipse.org/ecf/downloads.php
Download Source
Download both bundles: org.apache.zookeeper & org.eclipse.ecf.provider.zookeeper
from git server at http://git.eclipse.org/c/ecf/org.eclipse.ecf.git
If you use Eclipse, just open the git Repositories perspective, then copy everything between quotes here "http://anonymous@git.eclipse.org/gitroot/ecf/org.eclipse.ecf.git", and add a new git repository by pressing the button in the view toolbar.
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 we pick just one of our imaginary senario's and connect.
//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); // 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); // 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 "zoodiscovery.flavor.standalone", flavor "zoodiscovery.flavor.centralized" expects exactly one IP address, the location of ZooDiscovery instance you choose to let 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: z0, z1,z2 and z3 in this case). 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, then the whole orbiting ZooDiscovery members can neither discover nor publish services. 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
Automatically starting ZooDiscovery
By specifying the property
-Dzoodiscovery.autoStart
the Discovery provider will not require you to engage in ECF specific connection conversations. Instead this will done automatically. Please be aware that -Dzoodiscovery.autoStart=false will also autostart zoodiscovery. If you want to start ZooDiscovery yourself then omit the property alltogether.
Logging
Zoodiscovery works with the OSGi logging framework. Since this can be tedious to configure, you can supply a console log function:
-Dzoodiscovery.consoleLog
which will dump some discovery registrations to the console.
Please note that the main zookeeper library uses log4j. If you want logging from this you have to provide this yourself. Please see bug
https://bugs.eclipse.org/bugs/show_bug.cgi?id=337667
Fine tuning the underlying ZooKeeper
The class DefaultDiscoveryConfig contains text on how to pass zookeeper flags to the zookeeper instance.
In general, zookeeper flags (specific the ones that cannot be passed by the Java properties mechanism) can be passed by using the construct:
-Dzoodiscovery.zookeeperFlag=value
The description of these flags as well as other flags are found here