You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 6 Current »

THIS PAGE IS A WORK IN PROGRESS.

Overview

This page discusses generation and application of custom configuration elements, as well as their interaction with the ClusterConfigurationService interface.

Please note that, at time of writing, the ClusterConfigurationService is considered @Experimental.  Method signatures are subject to change prior to full support.

Background

Historically, cluster member configuration would be saved in a cache.xml, which would be provided to each member at startup.  This would require a cluster administrator to maintain individual XML files for each member.

Since Geode 1.0, we have provided as an alternative the cluster-configuration option.  In essence, using cluster configuration allowed a cluster's locator(s) would create, store and distribute the necessary XML files to members that require them when the member attempts to join the cluster.

We wish to remove our current reliance on the XML format, and to make available to extension developers a programatic API to interact with the cluster configuration.  This is discussed in more detail in the API proposal, linked below.

That public API will require an extension developer to indicate those classes that contain configuration information for their extension objects by having those classes implement CacheElement.  Please note that these configuration classes are themselves distinct from the instantiated object whose configuration they represent.

Currently, we use the Java architecture for XML Binding (JAXB) to marshal and unmarshal these configuration objects to and from the XML maintained by the configuration service.  As a result, configuration objects must contain the require JAXB annotations.  If an extension developer's configuration is defined by an XSD, these files can be easily generated via xjc.  An example generation script is provided below.

In a future iteration, the cluster configuration could sore configuration information not as XML, but as the raw configuration object itself.  From that point onward, the configuration object's fields will not require the JAXB annotations, as marshaling and unmarshaling to XML will no longer occur.

Additional API proposal and discussion will occur as that date draws closer.  In the interim, an extension developer's configuration classes will require the JAXB annotations so that it may interact properly with the existing services.

Note that all of the above discusses only those classes containing configuration information.  To reiterate, these configuration classes are themselves distinct from any instantiated extension object or service.  Until the internal reliance on XML has been overcome, it will remain that any extension class must implement Extension.

TODO: Extension is still an internal API.  Am I advocating the right approach here?

 

 

Placement of Configuration Elements within the Configuration

The configuration format is specified in the file cache-1.0.xsd.  This specification allows for custom configuration elements to appear in one of two places: in the top-level "cache" or in the child-level "region."  Remember that separate configuration information can exist for the entire cluster as well as each member group; the designation of "cache" and "region" is orthogonal to the designation of members to groups.  Do not confuse the "cache" level to mean a global configuration.  Every member group will define its own "cache" configuration.

The "cache" level is intended to contain configuration elements that effect cache member state and operation, where the "region" level is intended to contain configuration elements that effect management of data.  For instance, the Lucene Index configuration object will be stored as a "region" element, since it acts on a region's data.

When separation is not clear-cut, a developer may choose to store a configuration element in the "cache" that references a region.  For instance, the JDBC Connection configuration is stored as a "cache" element containing a region-name reference.  At the developer's discretion, this may be less a data concern and more a member I/O concern.

Where a particular configuration is kept is left to the developer's discretion.

Creating Configuration Classes

Whether your configuration element will exist at the "cache" level or at the "region" level, your custom configuration class should implement org.apache.geode.cache.configuration.CacheElement.  This requires your configuration object to be Serializable and Identifiable<String>.

If your custom configuration elements are themselves defined by an XSD, compatible configuration classes can be generated using JAXB.  A simple bindings file similar to that below can impose implementation of CacheElement on generated classes.  Episode files may also be passed as a JAXB binding to indicate that Geode configuration classes have already been generated.  These episode files can be found in <geode root>/etc/.

Generation can be performed by some IDEs, such as IDEA IntelliJ, and XJC plugins exist for Maven, Ant, and Gradle build systyems.  Alternatively, the command-line tool xjc can be used to generate classes.  As the common denominator example, we provide a command-line xjc task in each of :geode-core:geode-connectors, and :geode-lucene, reproduced below for your convenience.

JAXB generation provides a good starting point for your configuration classes.  However, manual adjustment of generated classes may be required.  For instance, if your configuration object does not define an id field, the generated class will need to be updated to implement a getId as part of the CacheElement interface.

Interacting with the Cluster Configuration Service

A typical use case is to create, modify, or remove your configuration object from the cache configuration service in a method belonging to a class extending GfshCommand.  The LuceneIndexCommands.java class demonstrates these cases.

The following pattern is useful in many of these commands.  First, the GfshCommand (acting on a locator to which you are connected) instantiates the configuration object, sets desired values, and distributes the configuration object (perhaps with additional parameters) to the cluster members that require configuration.  Each member then uses the configuration object to create / update / halt the service that the configuration defines.  Results are aggregated back to the locator.  The locator then decides, based on reported success or failures, if the cache configuration should be updated.   This is typically achieved by passing the ClusterConfigurationService the configuration object used.  For instance:

@CliCommand(value = LuceneCliStrings.LUCENE_CREATE_INDEX,
    help = LuceneCliStrings.LUCENE_CREATE_INDEX__HELP)
@CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA})
public Result createIndex(@CliOption(key = LuceneCliStrings.LUCENE__INDEX_NAME, mandatory = true,
    help = LuceneCliStrings.LUCENE_CREATE_INDEX__NAME__HELP) final String indexName,

    @CliOption(key = LuceneCliStrings.LUCENE__REGION_PATH, mandatory = true,
        optionContext = ConverterHint.REGION_PATH,
        help = LuceneCliStrings.LUCENE_CREATE_INDEX__REGION_HELP) final String regionPath,

    @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__FIELD, mandatory = true,
        help = LuceneCliStrings.LUCENE_CREATE_INDEX__FIELD_HELP) final String[] fields,

    @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER,
        help = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER_HELP) final String[] analyzers,
    @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__SERIALIZER,
        help = LuceneCliStrings.LUCENE_CREATE_INDEX__SERIALIZER_HELP) final String serializer) {

  // Every lucene index potentially writes to disk.
  authorize(Resource.CLUSTER, Operation.MANAGE, LucenePermission.TARGET);

  // Build Lucene Index Configuration object
  Index indexConfig = new Index();
  indexConfig.setName(indexName);

  DeclarableType declarableSerializer = new DeclarableType();
  declarableSerializer.setClassName(serializer);
  indexConfig.setSerializer(declarableSerializer);

  List<Index.Field> indexFields = new ArrayList<>();
  for (int i = 0; i < analyzers.length; i++) {
    Index.Field thisField = new Index.Field();
    thisField.setAnalyzer(analyzers[i].trim());
    thisField.setName(fields[i].trim());
  }
  indexConfig.getField().addAll(indexFields);

  // Perform the index creation on relevant members.
  LuceneIndexInfo indexInfo = new LuceneIndexInfo(indexConfig, regionPath);
  ResultCollector<?, ?> rc = executeFunctionOnAllMembers(createIndexFunction, indexInfo);
  List<CliFunctionResult> funcResults = (List<CliFunctionResult>) rc.getResult();

  // Build results display
  final TabularResultData tabularResult = ResultBuilder.createTabularResultData();
  for (final CliFunctionResult cliFunctionResult : funcResults) {
    tabularResult.accumulate("Member", cliFunctionResult.getMemberIdOrName());

    if (cliFunctionResult.isSuccessful()) {
      tabularResult.accumulate("Status", "Successfully created lucene index");
    } else {
      tabularResult.accumulate("Status", "Failed: " + cliFunctionResult.getMessage());
    }
  }

  // Determine if the configuration was successful and, if so, persist the change.
  boolean creationSuccessful =
      funcResults.stream().map(CliFunctionResult::isSuccessful).reduce(true, (x, y) -> x && y);
  if (creationSuccessful) {
    getConfigurationService().saveCustomRegionElement("cache", regionPath, indexConfig);
  }
  return ResultBuilder.buildResult(tabularResult);
}

 

See the documentation of the ClusterConfigurationService interface for more details.

Resources (TODO, resolve attachments / links)

  • No labels