This is an automated email from the ASF dual-hosted git repository.
jjramos pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push:
new 9fa4dc2 GEODE-6293: Fix fire & forget functions in gfsh (#3138)
9fa4dc2 is described below
commit 9fa4dc25c3f1dd9e470729eb707b3fe81fd7fc7e
Author: Juan José Ramos <[email protected]>
AuthorDate: Thu Jan 31 08:35:02 2019 +0000
GEODE-6293: Fix fire & forget functions in gfsh (#3138)
* GEODE-6293: Fix fire & forget functions in gfsh
- Fixed minor warnings.
- Refactored class `UserFunctionExecution`.
- Added unit tests for class `UserFunctionExecution`.
- Class `UserFunctionExecution` now supports the execution of functions
that don't return any results.
---
.../commands/ExecuteFunctionCommandDUnitTest.java | 66 ++++-
.../ExecuteFunctionCommandSecurityTest.java | 11 +-
.../cache/execute/CoreFunctionSecurityTest.java | 6 +-
.../cli/commands/ExecuteFunctionCommand.java | 7 +-
.../cli/functions/UserFunctionExecution.java | 171 +++++++----
.../cli/functions/UserFunctionExecutionTest.java | 328 +++++++++++++++++++++
...xecuteFunctionCommandWithSecurityDUnitTest.java | 4 +-
7 files changed, 502 insertions(+), 91 deletions(-)
diff --git
a/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandDUnitTest.java
b/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandDUnitTest.java
index 2296fd4..276c852 100644
---
a/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandDUnitTest.java
+++
b/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandDUnitTest.java
@@ -12,7 +12,6 @@
* or implied. See the License for the specific language governing permissions
and limitations under
* the License.
*/
-
package org.apache.geode.management.internal.cli.commands;
import static org.assertj.core.api.Assertions.assertThat;
@@ -32,6 +31,7 @@ import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.cache.execute.RegionFunctionContext;
+import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.assertions.CommandResultAssert;
@@ -39,7 +39,7 @@ import
org.apache.geode.test.junit.assertions.TabularResultModelAssert;
import org.apache.geode.test.junit.categories.GfshTest;
import org.apache.geode.test.junit.rules.GfshCommandRule;
-@Category({GfshTest.class})
+@Category(GfshTest.class)
public class ExecuteFunctionCommandDUnitTest {
@ClassRule
public static ClusterStartupRule cluster = new ClusterStartupRule();
@@ -47,22 +47,23 @@ public class ExecuteFunctionCommandDUnitTest {
@ClassRule
public static GfshCommandRule gfsh = new GfshCommandRule();
- private static MemberVM locator, server1, server2, server3;
private static final String functionId = "genericFunctionId";
-
private static String command = "execute function --id=" + functionId + " ";
@BeforeClass
public static void setUpClass() throws Exception {
- locator = cluster.startLocatorVM(0);
+ MemberVM locator = cluster.startLocatorVM(0);
gfsh.connectAndVerify(locator);
- server1 = cluster.startServerVM(1, "group1", locator.getPort());
- server2 = cluster.startServerVM(2, "group1", locator.getPort());
- server3 = cluster.startServerVM(3, "group2", locator.getPort());
- MemberVM.invokeInEveryMember(() -> {
- FunctionService.registerFunction(new GenericFunctionOp(functionId));
- }, server1, server2, server3);
+ MemberVM server1 = cluster.startServerVM(1, "group1", locator.getPort());
+ MemberVM server2 = cluster.startServerVM(2, "group1", locator.getPort());
+ MemberVM server3 = cluster.startServerVM(3, "group2", locator.getPort());
+ MemberVM.invokeInEveryMember(
+ () -> FunctionService.registerFunction(new
GenericFunctionOp(functionId)), server1, server2,
+ server3);
+ MemberVM.invokeInEveryMember(
+ () -> FunctionService.registerFunction(new FireAndForgetFunction()),
server1, server2,
+ server3);
// create a partitioned region on only group1
gfsh.executeAndAssertThat(
@@ -73,7 +74,9 @@ public class ExecuteFunctionCommandDUnitTest {
locator.waitUntilRegionIsReadyOnExactlyThisManyServers("/regionA", 2);
server1.invoke(() -> {
- Region region = ClusterStartupRule.getCache().getRegion("/regionA");
+ InternalCache cache = ClusterStartupRule.getCache();
+ assertThat(cache).isNotNull();
+ Region<String, String> region = cache.getRegion("/regionA");
region.put("a", "a");
region.put("b", "b");
});
@@ -267,7 +270,18 @@ public class ExecuteFunctionCommandDUnitTest {
"[GENERICFUNCTIONID-ARGUMENTS]", "[GENERICFUNCTIONID-ARGUMENTS]");
}
+ @Test
+ public void functionWithNoResults() {
+ TabularResultModelAssert tableAssert =
+ gfsh.executeAndAssertThat("execute function
--id=FireAndForget").statusIsSuccess()
+ .hasTableSection().hasRowSize(3).hasColumnSize(3);
+
+ tableAssert.hasColumn("Member").containsExactlyInAnyOrder("server-1",
"server-2", "server-3");
+ tableAssert.hasColumn("Status").containsExactlyInAnyOrder("OK", "OK",
"OK");
+ tableAssert.hasColumn("Message").containsExactlyInAnyOrder("[]", "[]",
"[]");
+ }
+ @SuppressWarnings("unused")
public static class MyPartitionResolver implements FixedPartitionResolver {
@Override
public String getPartitionName(final EntryOperation opDetails,
@@ -291,7 +305,6 @@ public class ExecuteFunctionCommandDUnitTest {
}
}
-
public static class GenericFunctionOp implements Function {
private String functionId;
@@ -300,6 +313,7 @@ public class ExecuteFunctionCommandDUnitTest {
}
@Override
+ @SuppressWarnings("unchecked")
public void execute(FunctionContext context) {
String filter = null;
if (context instanceof RegionFunctionContext) {
@@ -330,4 +344,30 @@ public class ExecuteFunctionCommandDUnitTest {
return functionId;
}
}
+
+
+ public static class FireAndForgetFunction implements Function {
+
+ FireAndForgetFunction() {}
+
+ @Override
+ public String getId() {
+ return "FireAndForget";
+ }
+
+ @Override
+ public boolean isHA() {
+ return false;
+ }
+
+ @Override
+ public boolean hasResult() {
+ return false;
+ }
+
+ @Override
+ public void execute(FunctionContext context) {
+ // Do Nothing.
+ }
+ }
}
diff --git
a/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandSecurityTest.java
b/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandSecurityTest.java
index f31e052..124dcc2 100644
---
a/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandSecurityTest.java
+++
b/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandSecurityTest.java
@@ -45,7 +45,7 @@ import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.categories.SecurityTest;
import org.apache.geode.test.junit.rules.GfshCommandRule;
-@Category({SecurityTest.class})
+@Category(SecurityTest.class)
public class ExecuteFunctionCommandSecurityTest implements Serializable {
@ClassRule
@@ -54,13 +54,13 @@ public class ExecuteFunctionCommandSecurityTest implements
Serializable {
@Rule
public GfshCommandRule gfsh = new GfshCommandRule();
- private static MemberVM locator, server1, server2;
+ private static MemberVM locator;
private static String REPLICATED_REGION = "replicatedRegion";
private static String PARTITIONED_REGION = "partitionedRegion";
@BeforeClass
- public static void beforeClass() throws Exception {
+ public static void beforeClass() {
Properties locatorProps = new Properties();
locatorProps.setProperty(SECURITY_MANAGER,
SimpleTestSecurityManager.class.getName());
locator = lsRule.startLocatorVM(0, locatorProps);
@@ -68,14 +68,15 @@ public class ExecuteFunctionCommandSecurityTest implements
Serializable {
Properties serverProps = new Properties();
serverProps.setProperty(ResourceConstants.USER_NAME, "clusterManage");
serverProps.setProperty(ResourceConstants.PASSWORD, "clusterManage");
- server1 = lsRule.startServerVM(1, serverProps, locator.getPort());
- server2 = lsRule.startServerVM(2, serverProps, locator.getPort());
+ MemberVM server1 = lsRule.startServerVM(1, serverProps, locator.getPort());
+ MemberVM server2 = lsRule.startServerVM(2, serverProps, locator.getPort());
Stream.of(server1, server2).forEach(server -> server.invoke(() -> {
FunctionService.registerFunction(new ReadFunction());
FunctionService.registerFunction(new WriteFunction());
InternalCache cache = ClusterStartupRule.getCache();
+ assertThat(cache).isNotNull();
cache.createRegionFactory(RegionShortcut.REPLICATE).create(REPLICATED_REGION);
cache.createRegionFactory(RegionShortcut.PARTITION).create(PARTITIONED_REGION);
}));
diff --git
a/geode-core/src/integrationTest/java/org/apache/geode/cache/execute/CoreFunctionSecurityTest.java
b/geode-core/src/integrationTest/java/org/apache/geode/cache/execute/CoreFunctionSecurityTest.java
index dab4fbe..98e6669 100644
---
a/geode-core/src/integrationTest/java/org/apache/geode/cache/execute/CoreFunctionSecurityTest.java
+++
b/geode-core/src/integrationTest/java/org/apache/geode/cache/execute/CoreFunctionSecurityTest.java
@@ -157,10 +157,8 @@ public class CoreFunctionSecurityTest {
@Test
@ConnectionConfiguration(user = "user", password = "user")
- public void functionRequireExpectedPermission() throws Exception {
- functionStringMap.entrySet().stream().forEach(entry -> {
- Function function = entry.getKey();
- String permission = entry.getValue();
+ public void functionRequireExpectedPermission() {
+ functionStringMap.forEach((function, permission) -> {
System.out.println("function: " + function.getId() + ", permission: " +
permission);
gfsh.executeAndAssertThat("execute function --id=" + function.getId())
.tableHasRowCount(RESULT_HEADER, 1)
diff --git
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommand.java
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommand.java
index 98dc99a..2111c44 100644
---
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommand.java
+++
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommand.java
@@ -76,7 +76,7 @@ public class ExecuteFunctionCommand extends
InternalGfshCommand {
}
if (dsMembers.size() == 0) {
- return new ResultModel().createError("No members found.");
+ return ResultModel.createError("No members found.");
}
// Build up our argument list
@@ -113,6 +113,7 @@ public class ExecuteFunctionCommand extends
InternalGfshCommand {
return ResultModel.createMemberStatusResult(results, false, false);
}
+ @SuppressWarnings("unused")
public static class ExecuteFunctionCommandInterceptor implements
CliAroundInterceptor {
@Override
public ResultModel preExecution(GfshParseResult parseResult) {
@@ -126,11 +127,11 @@ public class ExecuteFunctionCommand extends
InternalGfshCommand {
ResultModel result = new ResultModel();
if (moreThanOne) {
- return result.createError(CliStrings.EXECUTE_FUNCTION__MSG__OPTIONS);
+ return
ResultModel.createError(CliStrings.EXECUTE_FUNCTION__MSG__OPTIONS);
}
if (onRegion == null && filter != null) {
- return result.createError(
+ return ResultModel.createError(
CliStrings.EXECUTE_FUNCTION__MSG__MEMBER_SHOULD_NOT_HAVE_FILTER_FOR_EXECUTION);
}
diff --git
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecution.java
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecution.java
index 7503502..2836663 100644
---
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecution.java
+++
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecution.java
@@ -15,6 +15,7 @@
package org.apache.geode.management.internal.cli.functions;
import static
org.apache.geode.management.internal.cli.functions.CliFunctionResult.StatusState.ERROR;
+import static
org.apache.geode.management.internal.cli.functions.CliFunctionResult.StatusState.OK;
import java.util.ArrayList;
import java.util.Arrays;
@@ -36,6 +37,7 @@ import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.cache.execute.ResultCollector;
+import org.apache.geode.cache.query.RegionNotFoundException;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.ClassPathLoader;
import org.apache.geode.internal.cache.InternalCache;
@@ -50,17 +52,90 @@ import org.apache.geode.security.ResourcePermission;
* @since GemFire 7.0
*/
public class UserFunctionExecution implements InternalFunction<Object[]> {
+ private static final long serialVersionUID = 1L;
+ private static Logger logger = LogService.getLogger();
public static final String ID = UserFunctionExecution.class.getName();
- private static Logger logger = LogService.getLogger();
- private static final long serialVersionUID = 1L;
+ @Override
+ public boolean isHA() {
+ return false;
+ }
+
+ @Override
+ public String getId() {
+ return UserFunctionExecution.ID;
+ }
+
+ @Override
+ public Collection<ResourcePermission> getRequiredPermissions(String
regionName) {
+ return Collections.emptySet();
+ }
+
+ boolean loginRequired(SecurityService securityService) {
+ try {
+ // if the function is executed on a server with jmx-manager that user is
already logged into
+ // then we do not need to do login/logout here.
+ Subject subject = securityService.getSubject();
+ return subject == null || !subject.isAuthenticated();
+ } catch (AuthenticationRequiredException e) {
+ return true;
+ }
+ }
+
+ Function<?> loadFunction(String functionId) {
+ return FunctionService.getFunction(functionId);
+ }
+
+ String[] parseArguments(String argumentsString) {
+ if (argumentsString != null && argumentsString.length() > 0) {
+ return argumentsString.split(",");
+ } else {
+ return null;
+ }
+ }
+
+ Set<String> parseFilters(String filterString) {
+ if (filterString != null && filterString.length() > 0) {
+ return
Arrays.stream(filterString.split(",")).collect(Collectors.toSet());
+ } else {
+ return new HashSet<>();
+ }
+ }
+
+ ResultCollector parseResultCollector(String resultCollectorName)
+ throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
+ if (resultCollectorName != null && resultCollectorName.length() > 0) {
+ return (ResultCollector)
ClassPathLoader.getLatest().forName(resultCollectorName)
+ .newInstance();
+ } else {
+ return null;
+ }
+ }
+
+ Execution buildExecution(Cache cache, String onRegion) throws
RegionNotFoundException {
+ Execution execution;
+ DistributedMember member =
cache.getDistributedSystem().getDistributedMember();
+
+ if (onRegion != null && onRegion.length() > 0) {
+ Region region = cache.getRegion(onRegion);
+
+ if (region == null) {
+ throw new RegionNotFoundException(onRegion);
+ }
+
+ execution = FunctionService.onRegion(region);
+ } else {
+ execution = FunctionService.onMember(member);
+ }
+
+ return execution;
+ }
@Override
public void execute(FunctionContext<Object[]> context) {
Cache cache = ((InternalCache)
context.getCache()).getCacheForProcessingClientRequests();
DistributedMember member =
cache.getDistributedSystem().getDistributedMember();
- String[] functionArgs = null;
Object[] args = context.getArguments();
if (args == null) {
context.getResultSender().lastResult(new
CliFunctionResult(context.getMemberName(), ERROR,
@@ -74,39 +149,19 @@ public class UserFunctionExecution implements
InternalFunction<Object[]> {
String argumentsString = ((String) args[3]);
String onRegion = ((String) args[4]);
Properties credentials = (Properties) args[5];
-
SecurityService securityService = ((InternalCache)
context.getCache()).getSecurityService();
- boolean loginNeeded = false;
- try {
- // if the function is executed on a server with jmx-manager that user is
already logged into
- // then we do not need to do login/logout here.
- Subject subject = securityService.getSubject();
- loginNeeded = subject == null || !subject.isAuthenticated();
- } catch (AuthenticationRequiredException e) {
- loginNeeded = true;
- }
boolean loginSuccessful = false;
try {
- if (loginNeeded) {
+
+ // Authenticate If Needed
+ if (loginRequired(securityService)) {
securityService.login(credentials);
loginSuccessful = true;
}
- if (argumentsString != null && argumentsString.length() > 0) {
- functionArgs = argumentsString.split(",");
- }
- Set<String> filters = new HashSet<>();
- ResultCollector resultCollectorInstance = null;
- if (resultCollectorName != null && resultCollectorName.length() > 0) {
- resultCollectorInstance = (ResultCollector) ClassPathLoader.getLatest()
- .forName(resultCollectorName).newInstance();
- }
- if (filterString != null && filterString.length() > 0) {
- filters =
Arrays.stream(filterString.split(",")).collect(Collectors.toSet());
- }
-
- Function<?> function = FunctionService.getFunction(functionId);
+ // Load User Function
+ Function<?> function = loadFunction(functionId);
if (function == null) {
context.getResultSender()
.lastResult(new CliFunctionResult(context.getMemberName(), ERROR,
@@ -116,22 +171,16 @@ public class UserFunctionExecution implements
InternalFunction<Object[]> {
return;
}
- // security check
- function.getRequiredPermissions(onRegion,
functionArgs).forEach(securityService::authorize);
+ // Parse Arguments
+ Set<String> filters = parseFilters(filterString);
+ String[] functionArgs = parseArguments(argumentsString);
+ ResultCollector resultCollectorInstance =
parseResultCollector(resultCollectorName);
- Execution execution = null;
- if (onRegion != null && onRegion.length() > 0) {
- Region region = cache.getRegion(onRegion);
- if (region == null) {
- context.getResultSender().lastResult(
- new CliFunctionResult(context.getMemberName(), ERROR, onRegion +
" does not exist"));
- return;
- }
- execution = FunctionService.onRegion(region);
- } else {
- execution = FunctionService.onMember(member);
- }
+ // Security check
+ function.getRequiredPermissions(onRegion,
functionArgs).forEach(securityService::authorize);
+ // Build & Configure Execution Context
+ Execution execution = buildExecution(cache, onRegion);
if (execution == null) {
context.getResultSender()
.lastResult(new CliFunctionResult(context.getMemberName(), ERROR,
@@ -149,13 +198,20 @@ public class UserFunctionExecution implements
InternalFunction<Object[]> {
if (functionArgs != null && functionArgs.length > 0) {
execution = execution.setArguments(functionArgs);
}
+
if (filters.size() > 0) {
execution = execution.withFilter(filters);
}
- List<Object> results = (List<Object>)
execution.execute(function.getId()).getResult();
- List<String> resultMessage = new ArrayList<>();
+ // Execute Function and gather results
+ List results = null;
boolean functionSuccess = true;
+ List<String> resultMessage = new ArrayList<>();
+
+ ResultCollector rc = execution.execute(function.getId());
+ if (function.hasResult()) {
+ results = (List) rc.getResult();
+ }
if (results != null) {
for (Object resultObj : results) {
@@ -169,39 +225,26 @@ public class UserFunctionExecution implements
InternalFunction<Object[]> {
}
}
}
- context.getResultSender().lastResult(new
CliFunctionResult(context.getMemberName(),
- functionSuccess, resultMessage.toString()));
+ context.getResultSender().lastResult(new
CliFunctionResult(context.getMemberName(),
+ functionSuccess ? OK : ERROR, resultMessage.toString()));
+ } catch (RegionNotFoundException regionNotFoundException) {
+ context.getResultSender().lastResult(
+ new CliFunctionResult(context.getMemberName(), ERROR, onRegion + "
does not exist"));
} catch (ClassNotFoundException | IllegalAccessException |
InstantiationException e) {
context.getResultSender()
- .lastResult(new CliFunctionResult(context.getMemberName(), false,
+ .lastResult(new CliFunctionResult(context.getMemberName(), ERROR,
CliStrings.format(
CliStrings.EXECUTE_FUNCTION__MSG__RESULT_COLLECTOR_0_NOT_FOUND_ERROR_1,
resultCollectorName, e.getMessage())));
} catch (Exception e) {
logger.error("error executing function " + functionId, e);
context.getResultSender().lastResult(
- new CliFunctionResult(context.getMemberName(), false, "Exception: "
+ e.getMessage()));
+ new CliFunctionResult(context.getMemberName(), ERROR, "Exception: "
+ e.getMessage()));
} finally {
if (loginSuccessful) {
securityService.logout();
}
}
}
-
- @Override
- public Collection<ResourcePermission> getRequiredPermissions(String
regionName) {
- return Collections.emptySet();
- }
-
- @Override
- public String getId() {
- return UserFunctionExecution.ID;
- }
-
- @Override
- public boolean isHA() {
- return false;
- }
-
}
diff --git
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecutionTest.java
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecutionTest.java
new file mode 100644
index 0000000..b192ffc
--- /dev/null
+++
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/functions/UserFunctionExecutionTest.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ * or implied. See the License for the specific language governing permissions
and limitations under
+ * the License.
+ */
+package org.apache.geode.management.internal.cli.functions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.shiro.subject.Subject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+
+import org.apache.geode.cache.execute.Execution;
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.ResultCollector;
+import org.apache.geode.cache.execute.ResultSender;
+import org.apache.geode.cache.query.RegionNotFoundException;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.DistributedSystem;
+import org.apache.geode.distributed.internal.InternalDistributedSystem;
+import
org.apache.geode.distributed.internal.membership.InternalDistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.cache.InternalCacheForClientAccess;
+import org.apache.geode.internal.security.SecurityService;
+import org.apache.geode.security.AuthenticationRequiredException;
+
+public class UserFunctionExecutionTest {
+ private Object[] arguments;
+ private Execution execution;
+ private Function userFunction;
+ private UserFunctionExecution function;
+ private SecurityService securityService;
+ private ResultCollector resultCollector;
+ private FunctionContext<Object[]> context;
+ private ResultSender<Object> resultSender;
+ private InternalCacheForClientAccess filterCache;
+ private ArgumentCaptor<CliFunctionResult> resultCaptor;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ execution = mock(Execution.class);
+ userFunction = mock(Function.class);
+ context = mock(FunctionContext.class);
+ resultSender = mock(ResultSender.class);
+ function = spy(UserFunctionExecution.class);
+ securityService = mock(SecurityService.class);
+ resultCollector = mock(ResultCollector.class);
+ filterCache = mock(InternalCacheForClientAccess.class);
+ arguments = new Object[] {"TestFunction", "key1,key2",
"TestResultCollector", "arg1,arg2",
+ "/TestRegion", new Properties()};
+
+ when(userFunction.getId()).thenReturn("TestFunction");
+
+ InternalCache cache = mock(InternalCache.class);
+ DistributedSystem distributedSystem =
mock(InternalDistributedSystem.class);
+ DistributedMember distributedMember =
mock(InternalDistributedMember.class);
+ when(distributedMember.getId()).thenReturn("MockMemberId");
+
when(distributedSystem.getDistributedMember()).thenReturn(distributedMember);
+ when(filterCache.getDistributedSystem()).thenReturn(distributedSystem);
+
+ when(cache.getSecurityService()).thenReturn(securityService);
+ when(cache.getCacheForProcessingClientRequests()).thenReturn(filterCache);
+ when(context.getCache()).thenReturn(cache);
+ when(context.getArguments()).thenReturn(arguments);
+ when(context.getResultSender()).thenReturn(resultSender);
+
+ when(execution.withFilter(any())).thenReturn(execution);
+ when(execution.setArguments(any())).thenReturn(execution);
+ when(execution.withCollector(any())).thenReturn(execution);
+ when(execution.execute(anyString())).thenReturn(resultCollector);
+
+ doReturn(false).when(function).loginRequired(securityService);
+ doReturn(userFunction).when(function).loadFunction("TestFunction");
+
doReturn(resultCollector).when(function).parseResultCollector("TestResultCollector");
+ doReturn(execution).when(function).buildExecution(any(), any());
+
+ resultCaptor = ArgumentCaptor.forClass(CliFunctionResult.class);
+ }
+
+ @Test
+ public void testDefaultAttributes() {
+ assertThat(function.isHA()).isFalse();
+ assertThat(function.getId()).isEqualTo(UserFunctionExecution.ID);
+ assertThat(function.getRequiredPermissions(anyString())).isEmpty();
+ }
+
+ @Test
+ public void parseArgumentsTest() {
+ assertThat(function.parseArguments(null)).isNull();
+ assertThat(function.parseArguments("")).isNull();
+ assertThat(function.parseArguments("arg1,arg2")).isNotNull()
+ .isEqualTo(new String[] {"arg1", "arg2"});
+ }
+
+ @Test
+ public void parseFiltersTest() {
+ assertThat(function.parseFilters(null)).isNotNull().isEmpty();
+ assertThat(function.parseFilters("")).isNotNull().isEmpty();
+
assertThat(function.parseFilters("key1,key2")).isNotNull().containsOnly("key1",
"key2");
+ }
+
+ @Test
+ public void
buildExecutionShouldThrowExceptionWhenRegionIsRequiredButItDoesNotExist()
+ throws Exception {
+ when(filterCache.getRegion("region")).thenReturn(null);
+ when(function.buildExecution(any(), any())).thenCallRealMethod();
+
+ assertThatThrownBy(() -> function.buildExecution(filterCache, "region"))
+ .isInstanceOf(RegionNotFoundException.class);
+ }
+
+ @Test
+ public void loginRequiredShouldReturnTrueWhenSubjectIsNull() {
+ when(securityService.getSubject()).thenReturn(null);
+ when(function.loginRequired(securityService)).thenCallRealMethod();
+
+ assertThat(function.loginRequired(securityService)).isTrue();
+ }
+
+ @Test
+ public void loginRequiredShouldReturnTrueWhenSubjectIsNotAuthenticated() {
+ Subject subject = mock(Subject.class);
+ when(subject.isAuthenticated()).thenReturn(false);
+ when(securityService.getSubject()).thenReturn(subject);
+ when(function.loginRequired(securityService)).thenCallRealMethod();
+
+ assertThat(function.loginRequired(securityService)).isTrue();
+ }
+
+ @Test
+ public void
loginRequiredShouldReturnTrueWhenSecurityServiceFailsToLoadSubject() {
+ when(function.loginRequired(securityService)).thenCallRealMethod();
+ doThrow(new AuthenticationRequiredException("Dummy
Exception")).when(securityService)
+ .getSubject();
+
+ assertThat(function.loginRequired(securityService)).isTrue();
+ }
+
+ @Test
+ public void loginRequiredShouldReturnFalseWhenSubjectIsAuthenticated() {
+ Subject subject = mock(Subject.class);
+ when(subject.isAuthenticated()).thenReturn(true);
+ when(securityService.getSubject()).thenReturn(subject);
+ when(function.loginRequired(securityService)).thenCallRealMethod();
+
+ assertThat(function.loginRequired(securityService)).isFalse();
+ }
+
+ @Test
+ public void executeShouldFailWhenNoArgumentsAreProvided() {
+ when(context.getArguments()).thenReturn(null);
+
+ function.execute(context);
+
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage()).isEqualTo("Could not retrieve
arguments");
+ }
+
+ @Test
+ public void executeShouldFailWhenTargetFunctionCanNotBeLoaded() {
+ doReturn(null).when(function).loadFunction(anyString());
+
+ function.execute(context);
+
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage())
+ .isEqualTo("Function : TestFunction is not registered on member.");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void executeShouldFailWhenResultCollectorCanNotBeInstantiated()
throws Exception {
+ CliFunctionResult result;
+
+ doThrow(new
ClassNotFoundException("ClassNotFoundException")).when(function)
+ .parseResultCollector(anyString());
+ function.execute(context);
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage()).isEqualTo(
+ "ResultCollector : TestResultCollector not found. Error :
ClassNotFoundException");
+ reset(resultSender);
+
+ doThrow(new
IllegalAccessException("IllegalAccessException")).when(function)
+ .parseResultCollector(anyString());
+ function.execute(context);
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage()).isEqualTo(
+ "ResultCollector : TestResultCollector not found. Error :
IllegalAccessException");
+ reset(resultSender);
+
+ doThrow(new
InstantiationException("InstantiationException")).when(function)
+ .parseResultCollector(anyString());
+ function.execute(context);
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage()).isEqualTo(
+ "ResultCollector : TestResultCollector not found. Error :
InstantiationException");
+ reset(resultSender);
+ }
+
+ @Test
+ public void executeShouldFailWhenRegionIsSetAsArgumentButItDoesNotExist()
throws Exception {
+ when(function.buildExecution(any(), any())).thenCallRealMethod();
+ function.execute(context);
+
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage()).isEqualTo("/TestRegion does not
exist");
+ }
+
+ @Test
+ public void executeShouldFailWhenExecutorCanNotBeLoaded() throws Exception {
+ doReturn(null).when(function).buildExecution(any(), any());
+
+ function.execute(context);
+
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isFalse();
+ assertThat(result.getStatusMessage()).isEqualTo(
+ "While executing function : TestFunction on member : MockMemberId one
region : /TestRegion error occurred : Could not retrieve executor");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void executeShouldProperlyConfigureExecutionContext() {
+ Set<String> filter = new HashSet<>();
+ filter.add("key1");
+ filter.add("key2");
+
+ arguments = new Object[] {"TestFunction", "key1,key2",
"TestResultCollector", "arg1,arg2",
+ "/TestRegion", new Properties()};
+ when(context.getArguments()).thenReturn(arguments);
+ function.execute(context);
+ verify(execution, times(1)).withFilter(filter);
+ verify(execution, times(1)).withCollector(resultCollector);
+ verify(execution, times(1)).setArguments(new String[] {"arg1", "arg2"});
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult resultFullArguments = resultCaptor.getValue();
+ assertThat(resultFullArguments.isSuccessful()).isTrue();
+
+ reset(resultSender);
+ reset(execution);
+ arguments = new Object[] {"TestFunction", "", "", "", "", new
Properties()};
+ when(context.getArguments()).thenReturn(arguments);
+ function.execute(context);
+ verify(execution, never()).withFilter(any());
+ verify(execution, never()).setArguments(any());
+ verify(execution, never()).withCollector(any());
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult resultNoArguments = resultCaptor.getValue();
+ assertThat(resultNoArguments.isSuccessful()).isTrue();
+ }
+
+ @Test
+ public void executeShouldWorkProperlyForFunctionsWithResults() {
+ when(userFunction.hasResult()).thenReturn(true);
+ doReturn(true).when(function).loginRequired(any());
+ when(resultCollector.getResult()).thenReturn(Arrays.asList("result1",
"result2"));
+
+ function.execute(context);
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isTrue();
+ assertThat(result.getStatusMessage()).isEqualTo("[result1, result2]");
+ verify(securityService, times(1)).login(any());
+ verify(securityService, times(1)).logout();
+ }
+
+ @Test
+ public void executeShouldWorkProperlyForFunctionsWithoutResults() {
+ when(userFunction.hasResult()).thenReturn(false);
+ doReturn(true).when(function).loginRequired(any());
+
+ function.execute(context);
+ verify(resultSender, times(1)).lastResult(resultCaptor.capture());
+ CliFunctionResult result = resultCaptor.getValue();
+ assertThat(result.isSuccessful()).isTrue();
+ assertThat(result.getStatusMessage()).isEqualTo("[]");
+ verify(securityService, times(1)).login(any());
+ verify(securityService, times(1)).logout();
+ }
+}
diff --git
a/geode-web/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandWithSecurityDUnitTest.java
b/geode-web/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandWithSecurityDUnitTest.java
index 45fb534..6cc148b 100644
---
a/geode-web/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandWithSecurityDUnitTest.java
+++
b/geode-web/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExecuteFunctionCommandWithSecurityDUnitTest.java
@@ -37,7 +37,7 @@ import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.categories.GfshTest;
import org.apache.geode.test.junit.rules.GfshCommandRule;
-@Category({GfshTest.class})
+@Category(GfshTest.class)
public class ExecuteFunctionCommandWithSecurityDUnitTest {
@ClassRule
public static ClusterStartupRule lsRule = new ClusterStartupRule();
@@ -48,7 +48,7 @@ public class ExecuteFunctionCommandWithSecurityDUnitTest {
private static MemberVM locator;
@BeforeClass
- public static void beforeClass() throws Exception {
+ public static void beforeClass() {
locator = lsRule.startLocatorVM(0,
l ->
l.withHttpService().withSecurityManager(SimpleTestSecurityManager.class));