This articles talks about changes required to add a new gfsh command or parameter for a Geode feature (operated using Gfsh).
这份文档介绍如何在geode中增加一个gfsh命令。
1. Adding a new Gfsh command
Take an existing Gfsh command as reference and add/create file for the new command similarly.
All the supported commands can be found under "geode-core/src/main/java/com/gemstone/gemfire/management/cli/commands".
E.g.: QueueCommands.java (to create AsyncEventQueue) and IndexCommands.java (to list, create, destroy OQL index)
Now let's use IndexCommands.java as a prototype, to add a lucene index command as example.
1.1 Components of a gfsh command
IndexCommands.java contains commands for OQL index. For example:
- list index
- create index
- destroy index
For each of above command, there's a *Function.java class under functions/ directory, such as ListIndexFunction.java, CreateIndexFunction.java, DestroyIndexFunction.java.
The CliStrings.java defines constants for commands.
There's also a domain/IndexDetails.java for display the index definitions, and a ./domain/IndexInfo.java to pass index related information to functons.
1.2 Loading commands
How a new command is loaded and recognized by gfsh?
CommandManager.java will load commands in following sequence:
- user cmmands
- plugin commands
- CommandMarker classes under package com.gemstone.gemfire.management.internal.cli.commands
- Converter classes under package com.gemstone.gemfire.management.internal.cli.converters
- Roo's Converters
If we defined a new command in a plugin in a new package, such as Lucene index commands, we need to define a text file named "org.springframework.shell.core.CommandMarker" under xxx/src/main/resources/META-INF/services/ directory. xxx should be the package's root directory where the new command will be added. In our example, Lucene index command will be added into geode-lucene/ directory.
The text file name has to be the full package name of CommandMarker. Its content should be the class name extends CommandMarker, for example,
com.gemstone.gemfire.cache.lucene.internal.cli.LuceneIndexCommands
1.2 Function of a command
Each command needs to define a function. CliUtil provided helper to execute the function on target members, for example:
final Set<DistributedMember> targetMembers = CliUtil.findAllMatchingMembers(groups, null);
return CliUtil.executeFunction(createIndexFunction, indexInfo, targetMembers);
1.3 Example of adding Lucene List Index and create index command
In above step, we have added a text file "org.springframework.shell.core.CommandMarker" under geode-lucene/src/main/resources/META-INF/services/ directory. The text file's content is:
com.gemstone.gemfire.cache.lucene.internal.cli.LuceneIndexCommands
Create a class com.gemstone.gemfire.cache.lucene.internal.cli.LuceneIndexCommands:
@CliCommand(value = LuceneCliStrings.LUCENE_LIST_INDEX, help = LuceneCliStrings.LUCENE_LIST_INDEX__HELP)
@CliMetaData(shellOnly = false, relatedTopic={CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
@ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
public Result listIndex() {
....
}
@CliCommand(value = LuceneCliStrings.LUCENE_CREATE_INDEX, help = LuceneCliStrings.LUCENE_CREATE_INDEX__HELP)
@CliMetaData(shellOnly = false, relatedTopic={CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA }, writesToSharedConfiguration=true)
public Result createIndex(
@CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__NAME,
mandatory=true,
help = LuceneCliStrings.LUCENE_CREATE_INDEX__NAME__HELP) final String indexName,
@CliOption (key = LuceneCliStrings.LUCENE_CREATE_INDEX__REGION,
mandatory = true,
optionContext = ConverterHint.REGIONPATH,
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)
@CliMetaData (valueSeparator = ",") final String[] fields,
@CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER,
mandatory = false,
unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE,
help = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER_HELP)
@CliMetaData (valueSeparator = ",") final String[] analyzers,
@CliOption (key = LuceneCliStrings.LUCENE_CREATE_INDEX__GROUP,
optionContext = ConverterHint.MEMBERGROUP,
unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE,
help = LuceneCliStrings.LUCENE_CREATE_INDEX__GROUP__HELP)
@CliMetaData (valueSeparator = ",") final String[] groups) {
....
}
The notations define the command name and parameters.
Define function classes:
public class LuceneListIndexFunction extends FunctionAdapter implements InternalEntity {
....
}
public class LuceneCreateIndexFunction extends FunctionAdapter implements InternalEntity {
...
}
/* Only show these commands as available when gfsh is online */
@CliAvailabilityIndicator({LuceneCliStrings.LUCENE_SEARCH_INDEX, LuceneCliStrings.LUCENE_CREATE_INDEX,
LuceneCliStrings.LUCENE_DESCRIBE_INDEX, LuceneCliStrings.LUCENE_LIST_INDEX})
public boolean indexCommandsAvailable() {
return (!CliUtil.isGfshVM() || (getGfsh() != null && getGfsh().isConnectedAndReady()))
}
1.4 Tests
For each command, need at least dunit test and junit tests.
LuceneIndexCommandsDUnitTest.java
LuceneIndexCommandsJUnitTest.java
LuceneListIndexFunctionJUnitTest.java
LuceneCreateIndexFunctionJUnitTest.java
2. Adding new parameter to existing command
Here we are using implementation of new API "ignoreEvictionAndExpiration()" with AsyncEventQueue as an example.
The Gfsh command to create AsynEventQueue is defined in "QueueCommands.java".
gfsh>create async-event-queue --id=value --listener=value [--group=value] [--batch-size=value] [--persistent(=value)?] [--disk-store=value] [--max-queue-memory=value] [--listener-param=value(,value)*]
To support "ignoreEvictionAndExpiration()" new parameter "ignore-eviction-expiration" is added to AsyncEventQueue creation command.
Add new parameter reference
In "geode-core/src/main/java/com/gemstone/gemfire/management/cli/i18n/CliStrings.java", add the new command and help text.
public static final String CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION = "ignore-eviction-expiration";
public static final String CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION__HELP = "Whether to ignore eviction and expiration events.";
Changes to parameter set
Open the Corresponding Gfsh commands file.
E..g: "geode-core/src/main/java/com/gemstone/gemfire/management/cli/commands/QueueCommands.java"
Look for @CliOption
Add the new parameter (copy/paste one of the existing command and change it to reflect new command)
E.g:
@CliOption(key = CliStrings.CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION,
unspecifiedDefaultValue = "true",
specifiedDefaultValue = "true",
help = CliStrings.CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION__HELP)
Boolean ignoreEvictionAndExpiration,
Passing new parameter to associated CreateAsyncEventQueue Function.
In the Gfsh commands file, add new parameter to the argument list passed to function executed by Gfsh command.
AsyncEventQueueFunctionArgs aeqArgs = new AsyncEventQueueFunctionArgs(id, parallel,
enableBatchConflation, batchSize,batchTimeInterval,
persistent, diskStore, diskSynchronous, maxQueueMemory, dispatcherThreads, orderPolicy,
gatewayEventFilters, gatewaySubstitutionListener, listener, listenerProperties,
ignoreEvictionAndExpiration);
ResultCollector<?, ?> rc = CliUtil.executeFunction(new CreateAsyncEventQueueFunction(), aeqArgs, targetMembers);
Changes in Associated function
Make changes in the function to handle the new command parameter.
E.g.:
In this case "ignore-eviction-expiration" command invokes/sets the value for "AsyncEventQuey.ignoreEvictionAndExpiration()" attribute.
open "geode-core/src/main/java/com/gemstone/gemfire/management/internal/cli/functions/CreateAsyncEventQueueFunction.java"
Read the args and use it:
AsyncEventQueueFactory asyncEventQueueFactory = cache.createAsyncEventQueueFactory().
.setIgnoreEvictionAndExpiration(aeqArgs.isIgnoreEvictionAndExpiration())
Corresponding changes to support REST API
Add "RequestParam" and "Command Option" in the REST API controller.
E.g.: REST API For AsyncEventQueue creation:
in "geode-core/src/main/java/com/gemstone/gemfire/management/internal/web/controllers/QueueCommandsController.java"
@RequestParam(value = CliStrings.CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION, defaultValue = "true") final Boolean isIgnoreEvictionAndExpiration,
command.addOption(CliStrings.CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION, String.valueOf(isIgnoreEvictionAndExpiration));
Unit Tests:
geode-core/src/main/java/com/gemstone/gemfire/management/cli/commands/QueueCommandsDUnitTest.java
src/test/java/com/com/gemstone/gemfire/management/internal/configuration/SharedConfigurationEndToEndDUnitTest.java
@CliAvailabilityIndicator({LuceneCliStrings.LUCENE_SEARCH_INDEX, LuceneCliStrings.LUCENE_CREATE_INDEX, | ||
+ LuceneCliStrings.LUCENE_DESCRIBE_INDEX, LuceneCliStrings.LUCENE_LIST_INDEX}) | ||
+ public boolean indexCommandsAvailable() { | ||
+ return (!CliUtil.isGfshVM() || (getGfsh() != null && getGfsh().isConnectedAndReady())); | ||
+ } |