Overview
Currently, the only way for a command developer to modify cluster configuration is through internal methods or to manually edit the configuration xml file while the cluster is offline.
It can be expected that a module developer who create commands will need to modify the cluster configuration as well, therefore, we should expose a public API to retrieve or manipulate the cluster configuration persisted by the locator.
Background
The cache-1.0.xsd
defines the permissible structure of the cluster config, currently represented in XML.
Developers can include their own cluster configuration elements in one of two places as defined in the cache-1.0.xsd
: at the cache level or at the region level.
These accept any XML element, so long as the namespace does not match Geode's (i.e., {{http://geode.apache.org/schema/cache}}).
Goals
- The API is intended to be used by sub-module developers to create their own commands that would need to access/modify the cluster configuration.
- The API is intended to ONLY update the cluster or server-group's configuration saved by the locator(s).
- The API should not be tied with XML. We should be able to change how we save cluster configuration internally without changing the api.
Anti-Goals
- This API is not to be confused with an API that app-developers can use from client or from servers to, say, create a region and update the cluster configuration at the same time. That API would entail a remote invocation of this lower-level api, but it's not the same.
Approach
- Leverage JAXB to generate an initial set of configuration objects from cache-1.0.xsd. These will be used internally to marshall/unmarshll from/to xml. Developers only need to work with these configuration objects, not directly with xml.
- Module developers will use their own xsd to create module specific configuration object. The api should allow developers to pass in these objects and be saved in the cluster configuration.
- Expose a public
ClusterConfigurationService
via a public abstract command.
Proposal
As a minimum viable product, we would require only two methods: a getter and an updater.
public interface ConfigurationPersistenceService { /** * @param group The group to which the cluster configuration applies. default is "cluster" * @param additionalBindClass custom element classes created by other modules if present. * @return The cluster configuration for the specified group. */ CacheConfig getCacheConfig(String group); /** * @param group The group to which the cluster configuration applies. default is "cluster" * @param mutator Specification of how the cluster configuration should be altered. * @param additionalBindClass custom element classes created by other modules if present. */ void updateCacheConfig(String group, UnaryOperator<CacheConfig> mutator); }
Intended Use:
Given an interface as the above, we would be able to implement, for instance, the CreateIndexCommand
instead as
public class CreateIndexCommand implements SingleGfshCommand { private static final CreateIndexFunction createIndexFunction = new CreateIndexFunction(); @CliCommand(value = CliStrings.CREATE_INDEX, help = CliStrings.CREATE_INDEX__HELP) @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA}) @ResourceOperation(resource = ResourcePermission.Resource.CLUSTER, operation = ResourcePermission.Operation.MANAGE, target = ResourcePermission.Target.QUERY) public Result createIndex(...){ Result result; final Set<DistributedMember> targetMembers = findMembers(group, memberNameOrID); if (targetMembers.isEmpty()) { return ResultBuilder.createUserErrorResult(CliStrings.NO_MEMBERS_FOUND_MESSAGE); } RegionConfig.Index index = new RegionConfig.Index(); index.setName(indexName); index.setExpression(indexedExpression); index.setFromClause(regionPath); if (indexType == IndexType.PRIMARY_KEY) { index.setKeyIndex(true); } else { index.setKeyIndex(false); index.setType(indexType.getName()); } List<CliFunctionResult> functionResults = executeAndGetFunctionResult(createIndexFunction, index, targetMembers); result = ResultBuilder.buildResult(functionResults); result.setConfigObject(index); return result; } @Override public void updateClusterConfig(String group, CacheConfig config, Object element) { RegionConfig.Index index = (RegionConfig.Index) element; String regionPath = getValidRegionName(index.getFromClause(), config); RegionConfig regionConfig = config.findRegionConfiguration(regionPath); if (regionConfig == null) { throw new EntityNotFoundException("Region " + index.getFromClause() + " not found."); } regionConfig.getIndex().add(index); }
Here, we declaratively build the RegionConfig.Index
object we would like to add to the configuration.
On successfull creation, we search for the RegionConfig
object to which the index configuration should belong and add this index to that array.
With the mutator defined, we execute (a concurrency-safe) update to the cluster configuration.
Long-term Goals
- Reduce duplication of effort by replacing "creation" classes (i.e.,
CacheCreation
,RegionCreation
,BindingCreation
, et al) with JAXB-generated classes. - Remove requirement of JAXB / XML annotations on configuration element classes.
- This api is meant to be used by commands that would modify the Cache configuration, e.g. create region, create gateway receiver etc. It's only available where these commands are executed. After this, we might expose another set of java API that would be available in other nodes like client or servers. That api would allow developer to create a region as well as modifying the cluster configuration at the same time.
Resources
- JAXB generated CacheConfig class: CacheConfig.java
- An example of an extension's CacheElement: ConnectorService.java