Using distributed configuration

Subtopics

Related topics

Understanding distributed configuration

The IBM Director Distributed Configuration service allows a block of data to be stored on the IBM Director Server and associated with one or more managed objects. This block of data is then sent to the systems represented by the associated managed objects. As updates occur to the data block, the new changes are automatically sent to the associated systems, providing a simple yet powerful tool for distributing common data to a set of systems, and insuring that modifications to that data are propagated to those systems in a timely fashion. Furthermore, if the Distributed Configuration service determines that a given target system is unavailable (for example, is offline), it will send the updates to that system at the earliest opportunity (for example, when it determines that the system is online again).

The Distributed Configuration service also provides support for data to be stored on the client where the agent resides and pushed up to the server when modified. The agent might register with the server indicating a agent-owned block of data. As the data is updated on the system, updates are communicated to the server automatically. In combination, the use of both of these facilities allows for relatively nonvolatile data to be synchronized between a server and a set of client systems with a minimum of network traffic.

Records have the capability to be associated with group objects (TWGFilter objects). The Distributed Configuration service responds to changes in group membership; thus, records associated with a group can be sent automatically to new systems when new members are added to a group, and can be automatically deleted from a target system when a member is removed from a group.

Each data block is a record in the Distributed Configuration service.

Records

Records might be either server-based or client-based. This determines which record is assumed to be the master record when a mismatch occurs. When the Distributed Configuration manager determines that the records are out of sync between the server and the client, the master record is used as the template to update the out-of-date record.

The privacy of a record might be set at the time of the record creation. A private record might only be modified by the defining application, while public records might be freely modified by other tasks.

Since the distribution of data to group objects might not be appropriate for some operations, it is possible to specify a flag that indicates that the association of a record with a group object is not allowed.

Each record has an associated record ID which uniquely identifies that record (hierarchical names, such as "Application|Subtask|ID" are suggested, to minimize the possibility of name overlap).

Creating a distributed configuration record

The first step is to create a TWGDistConfigRecordDef object. Since TWGDistConfigRecordDef is a persistent object, it only needs to be created once. Typically, this will be done during extension initialization in the class instance initialization phase.

  public class DistCfgExtension extends TWGExtension {
  // ...

    final public static String RECORD_KEY = "UniqueRecordKey";

    public void InitClassInstances() throws TWGExtensionInitException
    {
      TWGDistConfigRecordDef rec = TWGDistConfigRecordDef.findByIDString(RECORD_KEY);
      if(rec == null) {
         try {
             // Create public server-based record definition which supports
             //    all managed object classes.  Data is stored in file
             //    DistCfgData.dcf on server.
             rec = new TWGDistConfigRecordDef(RECORD_KEY, null, true, true, 
                 true, "DistCfgData", false);
             rec.save();
         } catch (TWGPersistentObjectSaveException posx) {
         } catch (DuplicateObjectIDException doidx) {
         }
      }
    }
  }

Both server and client-mastered records must be created before they can be used. Client-mastered records which have not been registered (created) on the server will be ignored by the Distributed Configuration service.

Modifying a distributed configuration record

Once a record has been created, it is modified by sending update commands to the local service manager.

Server

The following special subclasses of Command are provided to query and update server-mastered records:

Client

There are currently no specialized helper command classes to modify client-mastered records, so the command parameters must be assembled manually. The input parameter for the command has the following format:

DistConfigRecord structure Record ID
(UTF8 format)
Record data

The code to assemble the modification command might look like this:

    ULONG length = 0;
    Str *record_key = NULL;

    LPBYTE buf = NULL;
    Command *cmd = NULL;
    DistConfigRecord *drec = NULL;

    // calculate buffer length

    length += sizeof(DistConfigRecord);

    record_key = new Str("UniqueRecordKey");
    record_key->LocalToCompUnicode();
    record_key->CompUnicodeToUTF8();
    length += record_key->Length() + 1;

    length += data_length_in_bytes;
    
    // allocate buffer and fill in appropriate structure members
    buf = (LPBYTE)Command::AllocateParm(length);
    if ( buffer != NIL )
    {
        ULONG end_of_record = sizeof(DistConfigRecord); // use to calculate values for offset fields
        memset(buf, 0, length);
        drec = (DistConfigRecord *)buf;
        drec->object_id = 0;
        drec->flags = DCFGMGR_FLAGS_ISSYSTEM | DCFGMGR_FLAGS_ISAGENTOWNED;
        if (data_length_in_bytes == 0)
        {
            // if no data, assume this is a delete request
            drec->flags |= DCFGMGR_FLAGS_ISDELETED;
        }
        memset(drec->server_uid, 0, sizeof(drec->server_uid));
        drec->off_rec_id = end_of_record;

        memcpy((LPSTR)drec + end_of_record, (LPSTR)*record_key, record_key->Length() + 1);
        end_of_record += record_key->Length() + 1;
        drec->off_rec_data = end_of_record;
        drec->len_rec_data = data_length_in_bytes;

        memcpy((LPSTR)drec + end_of_record, data_buffer, data_length_in_bytes);

        cmd = new Command(SVCMGR_ADD_UPDATE_DEL_DCFGMGR_REC);
        cmd->SetDestinationAddress("SvcMgr");
        cmd->AttachInputParm(buf, length);

        serviceNode->SendCommand(*cmd);
        if (cmd->ReturnCode() == 0)
        {
            // successful
        }
        else
        {
            // handle error
        }

Receiving notification of record updates

The next step is to handle record update notifications. The methods to accomplish this differ based on whether the processing is being handled at the server or the client.

Server

There are two methods by which a server task might be informed of record updates. The first way is to add register a listener object which will be invoked when a record change occurs. This is done by invoking the registerChangeListener() and deregisterChangeListener() methods of the TWGDistConfigRecordDef class with an argument of type TWGDistConfigRecordChangeListener.

Alternately, a subclass of TWGDistConfigRecordDef can be created and the callback routines distConfigAgentRecordDeleted(), distConfigAgentRecordUpdated(), and handleUnsupportedDistConfigAgent() overridden. If a subclass TWGDistConfigRecordDef is created in order to override the callback routines, it is necessary to register the class names during the class initialization phase of extension initialization:

  public class DistCfgExtension extends TWGExtension {
  // ...

    public void InitClassRegistration() throws TWGExtensionInitException
    {
      try {
        String className = FULLY_QUALIFIED_SUBCLASS_CLASS_NAME;
        RegisterClass(className);
      }
      catch (ClassNotFoundException cnfx) {
      }
    }
  }

Client

#include "dcfgmgr.hpp"

DistConfigMgrSubscribeCmd(char *rec_id, long callback_cmd_code);
DistConfigMgrUnsubscribeCmd(char *rec_id, long callback_cmd_code);

Subscription to record change notifications uses Director's interprocess communication system to send a registration message to the client's local service manager. The client uses these subclasses of the Command class to register and deregister for which records it would like to be notified when changes occur. The record to be registered is identified by its record ID, which was specified when the record definition was originally created. The client also specifies a callback command code, which is a user-defined command code which will be sent by the server when notifying the client of a record change. After creating an instance of the registration or deregistration command, the client should send the command using its local service node (the constructors for these commands set the correct destination address automatically). When changes occur to the record, a new command using the specified callback command code will be sent to the local service node, where it can be processed as needed.

On a successful subscription request, the DistConfigMgrSubscribeCmd returns with the current record data, if available. The DistConfigMgrSubscribeCmd class contains methods to access this data:

    ULONG numRecordsReturned() /* Get number of records returned        */
    ULONG getObjectID(ULONG index) /* Get object ID (System or group ID) of record 'index'           */
    UCHAR *getServerUID(ULONG index) /* Get server unique ID of record 'index' : returns pointer to UCHAR[8]       */
    char *getRecordID(ULONG index) /* Get record ID of record 'index' : returns pointer to char * (UTF8)           */
    BOOL isManagedObjectID(ULONG index) /* Test to see if record 'index' object ID is system/managed object ID          */
    BOOL isGroupID(ULONG index) /* Test to see if record 'index' object ID is group/TWGFilter object ID         */
    BOOL isDeletedRecord(ULONG index) /* Test to see if record 'index' has been deleted (as opposed to updated/added) */
    UCHAR *getDataHashcode(ULONG index) /* Get data hashcode of record 'index' : returns pointer to UCHAR[16]      */
    ULONG getDataLength(ULONG index) /* Get data length of record 'index'                                            */
    void *getDataPointer(ULONG index) /* Get pointer to data of record 'index'                                        */

Processing record updates

The next step is to process record update notifications. Again, the process differs based on whether the handling is being done on the server or a client.

Server

The distConfigAgentRecordDeleted() and distConfigAgentRecordUpdated() callbacks are each passed an object of class TWGManagedObject and a long value. The TWGManagedObject reference is the managed object instance which was associated with the record. The long value is an object ID (either a managed object ID or a group ID) which indicates which subscription instance was responsible for triggering this notification. This allows a task registered to receive update notification to unambiguously identify whether a particular notification occurred via a registration for a specific managed object, or whether the notification was received because the managed object was a member of a group which registered to receive the record.

Client

The client receives a Command whose command code is the one specified when the subscription command was sent to register the agent for update notification.

    BOOL ServiceNodeSubclass::CommandReceived(Command &cmd)
    {
        DistConfigMgrSubscribeNotice *notice = NULL;
        switch (cmd.CommandCode())
        {
            case COMMAND_CODE_SPECIFIED_IN_DIST_CONFIG_MGR_SUBSCRIBE_CMD:
                notice = new DistConfigMgrSubscribeNotice(cmd);
                if (notice != NULL && notice->numRecordsReturned() > 0)
                {
                    for (int i = 0; i < notice->numRecordsReturned(); i++)
                    {
                        // process initial configuration records
                    }
                }
                break;
            // handle other commands
        }
        return TRUE;
    }

The DistConfigMgrSubscribeNotice is a utility class which wraps the command sent by the server in response to a record modification. The constructor for the DistConfigMgrSubscribeNotice class accepts a Command object as a parameter, which should be the command object returned by the server. DistConfigMgrSubscribeNotice is defined in dcfgmgr.hpp:

class DistConfigMgrSubscribeNotice {
public:
    ULONG numRecordsReturned() /* Get number of records returned        */
 
    ULONG getObjectID(ULONG index) /* Get object ID (System or group ID) of record 'index'           */
    UCHAR *getServerUID(ULONG index) /* Get server unique ID of record 'index' : returns pointer to UCHAR[8]       */
    char *getRecordID(ULONG index) /* Get record ID of record 'index' : returns pointer to char * (UTF8)           */
    BOOL isManagedObjectID(ULONG index) /* Test to see if record 'index' object ID is system/managed object ID          */
    BOOL isGroupID(ULONG index) /* Test to see if record 'index' object ID is group/TWGFilter object ID         */
    BOOL isDeletedRecord(ULONG index) /* Test to see if record 'index' has been deleted (as opposed to updated/added) */
    UCHAR *getDataHashcode(ULONG index) /* Get data hashcode of record 'index' : returns pointer to UCHAR[16]      */
    ULONG getDataLength(ULONG index) /* Get data length of record 'index'                                            */
    void *getDataPointer(ULONG index) /* Get pointer to data of record 'index'                                        */
};

The notify command will contain one or more record notifications; the count is obtained by using the numRecordsReturned method. Each record notification contains status information such as the object ID that this record was associated with, the unique server ID of the server which posted this notification, the tag used to refer to this record, and the record data itself. The status information for each record notification is accessed by passing a zero-based index parameter to the methods provided by the DistConfigMgrSubscribeNotice class.