HelmaGroups - an extension for Helma Object Publisher
    =====================================================

This package enables group-communication between different Helma Servers over the network taking advantage of JGroups Library (www.jgroups.org). A new global object group is added to the scripting environment and all changes in that object tree are immediately replicated to all members.

Helma prerequistes

Helma 1.3 is needed.

Installation

Unzip the distribution to your Helma home directory. A new subdirectory helmagroups will be created and helmagroups-0.7.jar and jgroups-all.jar are extracted to lib/ext.

Configuration

server.properties

To activate HelmaGroups for a server add this line to the server.properties file. This makes Helma load the extension at startup and add a global object group to every application.

extensions = helma.extensions.helmagroups.GroupExtension

JGroups stack

To create a group we first need a configuration of the network properties: This is located in an xml-file in the /helmagroups-directory. A JGroups stack consists of a number of protocols that handle group traffic. At the bottom is a protocol that does the actual transmission (UDP or TCP), above that come protocols that handle group membership (PING, TCPPING, MERGE2, GMS - group membership service etc) and some protocols that take care of messages (FRAG - divides up larger message into smaller pieces, NAKACK - reliable mcast message transission etc). Two default stacks are included with the extension, default.xml for multicast-setups (basically only useful in a LAN environment) and tcp.xml for tcp-setups that can run over a WAN too.

Multicast

The following parameters in the stack config have to be set in case of using the UDP protocol (default.xml):

<protocol-param name="mcast_addr" value="228.8.8.8"/>
<protocol-param name="mcast_port" value="45566"/>
<protocol-param name="bind_port"  value="46000"/>
<protocol-param name="port_range" value="1000"/>
<protocol-param name="ip_ttl"     value="32"/>
<protocol-param name="bind_addr"  value="192.168.10.10"/>

mcast_addr and mcast_port specify the multicast address this group is using. If you wan to run different groups each needs its own xml-configuration-file with either a different address or port here. ip_ttl specifies how far multicast packets travel on the network: If set to 0 they are only seen on the localhost, 32 is for the whole network, higher values may be needed if your router is forwarding multicast traffic to another network. bind_addr is only needed when your computer has more than one network adapter. bind_port and port_range define a number of ports used for group membership management (the full range needs to be open in the firewall too).

TCP

Configuration in case of a TCP stack (tcp.xml):

TCP:
<protocol-param name="start_port" value="7800"/>
<protocol-param name="bind_addr"  value="192.168.10.10"/>

TCPPING:
<protocol-param name="initial_hosts" value="www.helma.org[7800],classic.helma.at[7800]"/>
<protocol-param name="port_range" value="3"/>

In the TCP protocol start_port defines which port a new member tries to use first. If it is already taken (possibly be another group instance running on the same machine the port number is increased until a free port is found. bind_addr is again only necessary for multiple network adapters.

The TCPPING protocol is responsible for finding other group members. Hosts that can be contacted at startup are listed in inital_hosts, port_range defines how many port above the given are tried.

Mounting a group

An application can run different groups: The network- and stack-configuration for each group is located in an xml-file. This configuration can either be stored locally in the the helmahome/helmagroups-directory or is fetched from an url. To mount such a group to an application add this to the app.properties file:

group.<alias>          = <filename-of-config>
group.<alias>.writable = true | false
group.<alias>.sendMode = all | majority | first | none
or
group.<alias>          = http://<your-config-url>
group.<alias>.writable = true | false
group.<alias>.sendMode = all | majority | first | none

Each group is available as group.<alias> to the scripting environment. To be able to change a group from an application the writable property explicitly has to be set to true. The sendMode property defines how the group is going to handle write operations: in mode all it waits for all other group members to acknowledge a change operation, majority waits for 50% + 1 member, first is obvious. none sets the group to fire & forget mode - be careful with your group architecture as this can cause confusion if the group is fed from more than one member.

Debugging

In the /helmagroups/debug.properties different levels of debugging can be defined:

debug.helma = true

This makes the HelmaExtension log all its activities (each put/remove-operation is logged when sent and when received).

trace=true

This makes JGroups log all in- and outgoing messages/events.

trace=true
default_output=DEBUG STDOUT

This makes JGroups log all activities - use this only for debugging as it creates megabytes of log data within minutes.

Usage in the scripting environment

Basically, a new global object group is added to the scripting environment (like app or root etc). Below this object a tree of objects can be built, with all add-, modify- and remove-operations being transmitted to the network and replicated to all other helma servers in that group immediately. The JGroups-library takes care of transmitting these operations lossless, ordered and obtains the correct state during startup.

The group object is visible in all applications in the group. To this object new GroupObjects can be added. A GroupObject can have strings, numbers, dateobjects and other GroupObjects as properties, so a tree of GroupObjects and data can be built.

group.test = new GroupObject();
group.test.somekey = "some value";
group.test.propkey1 = new GroupObject();
group.test.propkey1.anotherkey = "another value";

As soon as a GroupObject is assigning to the tree, it can be seen by all other members of the group. From that point on, any change of a property will be replicated immediately to all other members. Please note that a change operation will block until it has been seen by all group members. So the above code would produce three updates of the group, better would be the following code:

var obj = new GroupObject();
obj.somekey1 = "somevalue1";
obj.somekey2 = "somevalue2";
group.test = obj; < now the whole object gets replicated

Unfortunately it's currently not possible to build a tree locally and then replicate all the objects.

Methods of a GroupObject

GroupObject.list()
returns an array containing all the properties that are GroupObjects.

var arr = group.test.list();

GroupObject.count()
Returns the number of properties that are GroupObjects.

GroupObject.waitFor(propName, x)
Waits for x millis and stops if a property propName is touched in the meantime (added/modified,removed). Can be used for example to wait until a login-server has logged a user in and returned its data to the tree.

GroupObject.unwrap(), GroupObject.wrap()
Returns a local copy of a replicated GroupObject, with just the primitive properties (but not the children) copied. This can be used to save network load if more than one property is changed in a row: Unwrap() the object, do all the changes and put it back in the tree by using the wrap() method. It is important to go this way and not just assigning it to the original position because otherwise the branch that existed below that point would get lost.

var obj = group.test.unwrap();
obj.key1 = "value1";
obj.key2 = "value2";
group.test.wrap(obj);

Please note that transactions are NOT supported, so if the script fails later in the execution changes that were made to replicated objects will remain.

RPC to the whole group

group.getRemote (groupname)
It is possible to execute requests in all applications that mount to a group. The syntax is similar to Helma's XmlRpc-API. First get a Remote-Object, then call a function on it. The Remote-object represents the root of the remote application, it is possible to descend deeper.

var r = group.getRemote ("sessions");
var arr = r.testfunction (123,456);
// or:    r.bogusobj.testfunction() .....

Calling a function returns an array of result objects each having four properties: result, error, host and app

for (var i=0; i<arr.length; i++) {
   res.write (arr[i].app + "@" + arr[i].host + " returned: ");
   res.writeln ((arr[i].result) ? arr[i].result : arr[i].error);
}

Details of a group / Modifying a group

For each group some additional information and methods are supplied by the top group object:

group.getContent (groupname)
group.getFullContent (groupname)

the structure of the tree (content) or the full content of the tree (fullContent).

group.size (groupname)
group.count (groupname)

the total number of GroupObjects in the group.

group.getMembers (groupname)
a list of all members of the group

group.getConnection (groupname)
the ip and portnumber(s) this group is connected to.

group.getConfig (groupname)
the configuration of this group (the JGroups stack)

group.getFullConfig (groupname)
the full configuration of this group (the JGroups stack including all properties)

group.isConnected (groupname)
returns true/false on wether the group is connected.

group.connect (groupname)
group.reconnect (groupname)
group.disconnect (groupname)

Remove or add the server from/to the group, connect() is done at startup automatically.

group.reset (groupname)
deletes all content from the group.

group.destroy (groupname)
makes all instances of the group disconnect.

group.log (message)
logs to the same logfile as the groupextension does. this is useful to have the log statements in the correct order as the internal messages by the extension (which is difficult to achieve if different logs are used).

last modified: 2004-03-28