Teach Yourself IceGrid in 10 Minutes

This article provides a short and simple introduction to IceGrid. The claim that you will be able to learn IceGrid in ten minutes is something of an exaggeration: to learn IceGrid thoroughly, you will have to invest more than ten minutes. However, this article will get you started and help you understand the concepts underlying IceGrid. In particular, this article covers how you can avoid manual endpoint administration and get a server activated on demand when a client invokes an operation on an object in that server. You will be surprised how easy this is — a few simple steps are sufficient to achieve it.

Once you have that out of the way, you can learn about the remaining features of IceGrid in the Ice Manual.

On this page:

Avoiding Hard-Wired Port Numbers

You have probably seen server-side code such as the following:

Java
// ...
com.zeroc.Ice.ObjectAdapter adapter = communicator.createObjectAdapterWithEndpoints("MyAdapter", "tcp -p 10000");
// ...

This is the simplest and most straightforward way of creating an object adapter. Unfortunately, it is also one of the most useless:

  • The server hard-wires the endpoint information into the source code. As a result, if you want to move the server to a different port for some reason, you will need to recompile the code.
  • You need to manually administer the port numbers that are used by servers because no two servers can listen on the same port. If you have a large number of servers, this rapidly becomes tedious.

To improve on this situation, you can pass the port information into the call to createObjectAdapterWithEndpoints, for example:

Java
// ...
com.zeroc.Ice.ObjectAdapter adapter = communicator.createObjectAdapterWithEndpoints("MyAdapter", args[0]);
// ...

This code allows you to pass the endpoint specification into the program as a command-line argument. We have rid ourselves of the hard-wired endpoint, but it is still awkward:

  • Ice already has a built-in mechanism for doing exactly the same thing.
  • You cannot use IceGrid's location and server activation features if you create the object adapter in this way.

Here is how to achieve the same thing properly:

Java
// ...
com.zeroc.Ice.ObjectAdapter adapter = communicator.createObjectAdapter("MyAdapter");
// ...

This code is identical, except that it calls createObjectAdapter instead of createObjectAdapterWithEndpoints. The code does not specify an endpoint for the adapter, so the Ice run time must use some other means to determine what endpoint to use. The implementation of createObjectAdapter behaves as follows:

  • If the property MyAdapter.Endpoints is not set, the run time creates the adapter without endpoints. Obviously, because such an adapter does not listen on any network interface for incoming requests, it is not useful for distributed computing. However, an adapter without endpoints is still useful for bidirectional communication.
  • Otherwise, the run time uses the value of MyAdapter.Endpoints to determine at what endpoint(s) the adapter will listen for incoming requests.

With this changed code, we can control the endpoint for the server's adapter from the command line, for example:

$ java MyServer.Main --Ice.Config=config

This assumes that the MyAdapter.Endpoints property is set in the configuration file config as follows:

MyAdapter.Endpoints=tcp -p 10000

In most programming languages, you can also set the ICE_CONFIG environment variable to the path name of the configuration file instead of using a command-line option.

With this configuration, the client can construct an initial proxy for an object in the server as usual: as long as the client knows the object identity and the endpoint, it can use a stringified proxy and pass that to stringToProxy. With the preceding configuration, assuming that the object identitiy of an object is Object1, the client can use the following stringified proxy to reach the object:

Object1:tcp -h somehost.xyz.com -p 10000

Using the IceGrid Location Service

By moving an object adapter's port number out of the source code, we have gained some flexibility because we now can run a server at a different port without having to recompile the code. However, if we have lots of servers, we still need to manually administer which port is used by what server. Moreover, because clients specify the server's port number in their stringified proxies, whenever we change the machine on which a server runs, or the port number at which it listens, we also need to update the configuration of all clients.

Clearly, it would be preferable to not be burdened with all this administrative overhead. Ideally, we want to be able to run servers on arbitrary machines and on arbitrary ports that are dynamically assigned by the operating system, and have clients bind to the servers without any change in configuration.

The location service that is built into IceGrid provides a neat solution for exactly this scenario. The IceGrid location service allows clients to dynamically (and transparently) acquire the current endpoint for a server, regardless of the machine and the port at which a server is running. Similarly, for servers, no ports need be configured. We can run a server on any machine and let the operating system choose a free port for the server, without having to administer anything.

The location service works by replacing the endpoint information in the client's proxy with a symbolic name, for example:

Object1@MyAdapter

Such a proxy is known as an indirect proxy, because the proxy will be bound to the server endpoint with an extra level of indirection via IceGrid. (In contrast, a proxy that includes a specific endpoint is known as a direct proxy.) When the client invokes an operation using an indirect proxy, the client-side run time contacts the IceGrid locator behind the scenes and asks for the machine and port at which MyAdapter can be found. If the server is running, the locator knows the endpoint for the adapter and returns that to the client. Once the client-side run time is aware of the actual endpoint, it then sends the request to the server. The entire process is transparent to application code and quite similar to the way the DNS resolves domain names to IP addresses.

The Ice run time also uses a number of optimizations and caching to prevent this extra level of indirection from becoming a performance bottleneck. Typically, this means that each client will contact the locator only once, the first time it binds to a particular endpoint; future invocations are sent directly to the server without first contacting the locator.

Servers keep the locator up-to-date by contacting it whenever they activate an object adapter: each server updates the locator with its adapter's current IP address and port number, so the locator can, in turn, pass that information to clients when they resolve an indirect proxy.

For all this to work, both clients and servers must agree to use the same locator. The location service is provided by the IceGrid registry, so this is the same as saying that clients and servers must agree to use the same registry. To do this, clients and servers must be configured with a single property, Ice.Default.Locator. This property specifies the proxy to the IceGrid location service and, if set, enables indirect binding for clients, as well as registration of endpoint details by servers. So, for both clients and servers, we simply need to set this property as shown in the example below:

Ice.Default.Locator=IceGrid/Locator:tcp -h registryhost.xyz.com -p 12000

We can set this property in a configuration file or on the command line for client and server. The proxy states that the locator runs on host registryhost.xyz.com, at port 12000, with the object identity IceGrid/Locator. (This is the default object identity of the IceGrid locator. You can change this identity by setting the property IceGrid.InstanceName.)

To enable indirect binding, we need to run the location service, that is, run the icegridregistry process. The registry requires a minimum of configuration:

IceGrid.Registry.Client.Endpoints=tcp -p 12000
IceGrid.Registry.Server.Endpoints=tcp
IceGrid.Registry.Internal.Endpoints=tcp
IceGrid.Registry.Data=db/registry

IceGrid.Registry.DynamicRegistration=1

The IceGrid.Registry.Client.Endpoints property determines the endpoint at which the location service runs. Your setting of Ice.Default.Locator for clients and servers must match the endpoint(s) in IceGrid.Registry.Client.Endpoints.

Note that the endpoint you specify with IceGrid.Registry.Client.Endpoints must use a fixed port number: it provides the one fixed point that clients and servers need in order to use indirect binding. (The locator proxy cannot be an indirect proxy because that would create a chicken-and-egg problem: to resolve the proxy to the locator, we need a locator, but we cannot find the locator without resolving the proxy...)

You must set the server and internal endpoint properties to one or more protocols, but you need not specify a specific port for these two properties; clients and servers find the actual endpoint by contacting the locator at run time.

The IceGrid.Registry.Data property specifies the path name to a directory in which the registry keeps its database.

Finally, you must set IceGrid.Registry.DynamicRegistration to a non-zero value. (Without this setting, servers will not be allowed to register their object adapter endpoints unless they have been explicitly deployed. We will return to explicit deployment shortly.)

Having specified these property settings in a file config.registry, you can run the registry as follows:

$ icegridregistry --Ice.Config=config.registry

For the server, you only need two properties to make the server register its adapter with the locator:

MyAdapter.Endpoints=tcp
MyAdapter.AdapterId=MyAdapter

Note that MyAdapter.Endpoints has changed: it now only specifies a protocol, but no longer specifies a port number. In effect, this says "I want MyAdapter to use TCP/IP, but I don't care about what port number it listens at." You also must set MyAdapter.AdapterId. For example, we could set this property as follows:

MyAdapter.AdapterId=FooBar

In that case, the proxy used by clients to bind to the server would look like this:

Object1@FooBar

The <adapter-name>.AdapterId property controls three things:

  • It tells the server-side run time to register the adapter with the locator.
  • It sets the ID by which the adapter is known to the locator and to clients.
  • It causes the adapter to produce indirect proxies instead of direct proxies.

The second point is particularly important: it allows two servers to use the same adapter name, such as MyAdapter, without causing a naming conflict in the registry: by assigning different adapter IDs to these adapters, they remain distinguishable to the registry and to clients. (Without such a renaming mechanism, all adapters in all servers would have to have unique names, which is difficult to ensure, especially if the servers are written by independent developers.) Adapter IDs are also useful for configuration because they allow tools to unambiguously refer to a specific adapter by its ID.

With these two properties set, once you start the server, the server contacts the registry and informs it of the endpoint details for MyAdapter and, when a client uses a proxy such as Object1@MyAdapter, it will correctly bind to the server, regardless of what machine the server runs on and at what port number it listens. You can see this magic in action if you set the Ice.Trace.Locator property on the client side, which shows you the behind-the-scenes activity during binding of indirect proxies.

You can very easily see all of this in action by modifying the demo in demo/Ice/hello a little bit. As it stands, this demo uses direct binding so, by converting it to use indirect binding via IceGrid instead, you can see exactly what is involved. Here are the changes that are necessary:

  • The server configuration sets the property Hello.Endpoints to the value
    tcp -p 10000:udp -p 10000:ssl -p 10001

    Change this property to the value
    tcp:udp:ssl
  • Add the following property setting to the server configuration:
    Hello.AdapterId=HelloAdapter
  • The client configuration sets the property Hello.Proxy to the value
    hello:tcp -p 10000:udp -p 10000:ssl -p 10001

    Change this property to the value
    hello@HelloAdapter
  • Set the property Ice.Default.Locator to the proxy of the registry in both client and server configurations.

Now, when you run the demo while the registry is running, the client uses indirect binding and the server uses a port that is assigned by the operating system from the ephemeral port range.

Activating Servers Automatically

With the configuration we just discussed, you can start a server and make the server's endpoint information available via IceGrid without having to manually configure host names and port numbers. However, all this works only for as long as the server is running. Often, this is not a problem: you can simply start the server when the machine boots (by making the appropriate start-up entries in /etc/rc.d or the Windows registry); once the server is up, you can forget about it and let it do its job. However, there are three drawbacks to this:

  • Maintaining initialization scripts or registry entries for many servers quickly becomes tedious.
  • Servers consume operating system resources even when they are idle.
  • Servers may crash or malfunction.

The second point is not too serious — the only thing a server consumes while it is idle is a slot in the process table, a few file descriptors, and swap space, none of which are normally scarce resources. However, the third point deserves more attention because a server can malfunction due to no fault of its own. For example, the operating system can run out of swap space and cause a memory allocation failure in the server. Depending on exactly where the problem occurs, the server code may simply give up and exit or, worse, misbehave in less obvious ways. (And the failure may occur in a third-party library that is used by the server and whose quality you cannot control.) Another scenario for unexpected server death is a system administrator who accidentally kills the wrong process. (Of course, the system administrator will usually in turn blame a buggy script...) The problem with manually started servers is that, well, they are started manually: if a server crashes, it stays down until someone re-starts it.

IceGrid provides a facility to activate servers on demand, when a client first invokes an operation. In a nutshell, automatic server activation is an add-on service to the location service: clients resolve indirect proxies in the usual way; however, if a server is not running at the time a client asks for the server's endpoint, the registry first starts the server and returns the endpoint details to the client once the server has activated its object adapter.

Server activation is taken care of by IceGrid nodes. You must run an IceGrid node on each machine on which you want IceGrid to start servers on demand. In addition, you must run a single IceGrid registry (not necessarily on one of the machines on which you run your application servers). It is the job of each IceGrid node to activate servers on the corresponding machine, to monitor the servers, and to make the servers' status available to the registry.

Frequently, you will run the IceGrid registry on the same machine as one of the IceGrid nodes; because this is a common deployment scenario, IceGrid allows you to combine the registry and a node into a single process by setting the IceGrid.Node.CollocateRegistry property to a non-zero value. In addition to the registry properties we used in the preceding section, the node also requires a few configuration properties:

# Registry configuration (as before)
IceGrid.Registry.Client.Endpoints=tcp -p 12000
IceGrid.Registry.Server.Endpoints=tcp
IceGrid.Registry.Internal.Endpoints=tcp
IceGrid.Registry.Data=db/registry

# Only required if you want servers to register
# themselves without explicit deployment. If all
# servers are deployed explicitly, this property
# can be left unset.
IceGrid.Registry.DynamicRegistration=1

# Node configuration
IceGrid.Node.CollocateRegistry=1
IceGrid.Node.Name=node1
IceGrid.Node.Endpoints=tcp
IceGrid.Node.Data=db/node

# Set the default locator so the node and admin
# tools can find the registry.
Ice.Default.Locator=IceGrid/Locator:tcp -h registryhost.xyz.com -p 12000

The additional node configuration sets IceGrid.Node.CollocateRegistry to indicate that the node should also act as a registry. The IceGrid.Node.Name property assigns a symbolic name to the node. This name can be anything — it serves to distinguish nodes that use the same registry, that is, the nodes of a registry must have unique names. The IceGrid.Node.Data property sets the path name of a directory in which the node stores information about its servers.

Now we can start a node that also includes a registry:

$ icegridnode --Ice.Config=config.icegrid

On the client side, no changes are required to make the client work with automatically-activated servers because all the work is done by the registry. To make the server work with automatic activation, we must make two changes:

  • update the server configuration
  • deploy the server

The first point is taken care of very easily: the server now requires no configuration at all, other than the setting of Ice.Default.Locator. In particular, we no longer need to specify an endpoint or an adapter ID because, as we will see in a moment, that configuration shifts from the server to the server's deployment.

To get IceGrid to activate the server on demand, we need to inform IceGrid of the particulars of the server. Here are the essential items of information that IceGrid needs to know so it can start the server:

  • an application name
  • a node name
  • a server identifier
  • the path name to the executable of the server
  • the name of the server's adapter
  • the protocol to be used by the server

IceGrid expects these items to be presented in a deployment descriptor. Deployment descriptors are written in XML. Here is the deployment descriptor for our example:

<icegrid>
  <application name="demo">
    <node name="node1">
      <server id="DemoServer" exe="/usr/bin/demoserver" activation="on-demand">
        <adapter name="MyAdapter" endpoints="tcp"/>
      </server>
    </node>
  </application>
</icegrid>

Much of this is self-explanatory. All of the information is presented as sub-elements of the icegrid element.

  • The application name identifies the deployment information for an application (which may have more than one server). In other words, the application name serves as a convenient handle when we need to identify a particular deployment (as we will see in a moment when we use the icegridadmin tool).
  • The node name identifies the machine on which the server will execute or, more precisely, it provides the name of the node that will be instructed to start the server — the server will execute on the machine that runs the node with that name. The node name is the same name that we configured earlier by setting the IceGrid.Node.Name property.
  • The server ID is a label that identifies the server. It allows us to refer to a particular server by name, for example, to enquire about the server's status with an administrative tool.
  • The exe attribute provides the path name of the server's executable. (Usually, you will use an absolute path name here because relative pathnames are interpreted relative to the node's working directory.)
  • The activation attribute specifies that the server should be activated on demand, when a client invokes an operation on one of the server's objects. (IceGrid also provides a number of other activation modes.)
  • The adapter element's name attribute must specify the adapter name that is used by the server. (You can also optionally set an id attribute; if that attribute is not set, the default adapter ID is <server-id>.<adapter-name>.)
  • The endpoints attribute specifies the protocol(s) to be used by the server.

Now that we have a deployment descriptor, we can deploy the application, that is, inform the IceGrid registry about these details:

$ icegridadmin --Ice.Config=config.icegrid -e 'application add demo.xml'

The -e option tells icegridadmin to execute the commands provided as the option argument. In this case, the add command tells the tool that we want to add the information in demo.xml to the registry database. Note that the command also points the tool at the configuration file for the registry and the node. The only property setting that is read by icegridadmin is Ice.Default.Locator, which the tool needs so it knows how to contact the location service.

This is all that is necessary to have your server activated on demand. Provided that you have deployed the server with the registry, it now starts automatically as soon as the first client tries to contact an object in that server.

Other IceGrid Features

This article only covers the basics of IceGrid to get you started. There are many other features in IceGrid, some quite sophisticated, such as replication and load balancing, allocation of particular servers for exclusive use by clients, and templates to simplify deployment and configuration of large numbers of servers. You can also arrange for a server to stop automatically once it has been idle for some time, to conserve machine resources. You can even arrange for software updates to be downloaded to a number of remote machines, allowing you to automatically update application software from a central point without intervention at the remote end. As usual, please consult the Ice Manual for more information on these features.

Conclusion

IceGrid makes it very easy to get away from manual port administration and, through indirect binding, allows you to move servers from one machine to another (for example, to balance machine load) without having to update the configuration of all deployed clients. In addition, the central administration of IceGrid allows you to deploy a large number of servers easily and efficiently, without getting overwhelmed by lots of detail.

See Also