DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
THIS PAGE IS A WORK IN PROGRESS.
Draft notes / TODOs:
- Episode files have not yet been committed to develop etc/
- Do we expect extension developers to have the Geode source? Can I be referencing
etc/*, or do we need to provide access to those files elsewhere? - Do we care that we find elements in linear-time? If we're requiring that each element is
Identifiable<String>, would a Map be better? I don't have the context for how many elements we expect to be in a single configuration XML. - LuceneCommands is not yet updated to the new style, but if all their methods will stay in a single file, I think it's a good reference for an extending developer.
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.
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. Alternatively, the command-line tool xjc can be used to generate classes. Refer to xjc task in each of :geode-core, :geode-connectors, and :geode-lucene for example command-line xjc commands, 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)
- Files referenced:
- XSD
- Bindings
- Episode files
- xjc tasks in builds
- LuceneIndexCommands
- LuceneIndexConfig class
- Also refer to
- Cluster Configuration Service Interface