Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

This articles talks about steps involved in adding a changes required to add a new gfsh command or parameter for a Geode feature that can be (operated using Gfsh.

Its still under work...

Needs to be added.

).

这份文档介绍如何在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  of new api API "ignoreEvictionAndExpiration()" with AsyncEventQueue as an example.

The Gfsh command to create AsynEventQueue is defined in "QueueCommands.java".

Changes to commands set

All Gfsh commands are listed in QueueCommands class file.

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: Open "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)

...

      Boolean ignoreEvictionAndExpiration,

 

CliUtil.executeFunction() — Make changes to pass new argument.

 ResultCollector<?, ?> rc = CliUtil.executeFunction(new CreateAsyncEventQueueFunction(),

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,

                    new Object[] { id, parallel, enableBatchConflation, batchSize,batchTimeInterval,

...

          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/commands/QueueCommandsQueueCommandsController.java"

  @RequestParam(value = CliStrings.CREATE_ASYNC_EVENT_QUEUE__IGNORE_EVICTION_EXPIRATION, defaultValue = "true") final Boolean isIgnoreEvictionAndExpiration,

 

cli/i18n/CliStrings.java

Add the new option command and help content.

public static final String command.addOption(CliStrings.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.";

 

Make changes to function call:

geode-core/src/main/java/com/gemstone/gemfire/management/internal/cli/functions/CreateAsyncEventQueueFunction.java

read the args and use it.

 final boolean ignoreEvictionAndExpiration = (Boolean) args[15];

 asyncEventQueueFactory.setIgnoreEvictionAndExpiration(ignoreEvictionAndExpiration);

, 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


 

 public static ResultCollector<?, ?> executeFunction(final Function function, Object args , final Set<DistributedMember> targetMembers) {

    Execution execution = null;

 

    if (args != null) {

      execution = FunctionService.onMembers(targetMembers).withArgs(args);

    } else {

      execution = FunctionService.onMembers(targetMembers);

    }

 

    ((AbstractExecution) execution).setIgnoreDepartedMembers(true);

    return execution.execute(function);

  }

 

geode-core/src/main/java/com/gemstone/gemfire/management/internal/web/controllers/QueueCommandsController.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()));
  

 

 

 

+ }