| /* |
| * 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.commands; |
| |
| import static org.apache.geode.cache.Region.SEPARATOR; |
| import static org.assertj.core.api.Assertions.assertThat; |
| |
| import java.util.Set; |
| |
| import org.apache.logging.log4j.util.Strings; |
| import org.junit.BeforeClass; |
| import org.junit.ClassRule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| |
| import org.apache.geode.cache.EntryOperation; |
| import org.apache.geode.cache.FixedPartitionResolver; |
| import org.apache.geode.cache.Region; |
| 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; |
| 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) |
| public class ExecuteFunctionCommandDUnitTest { |
| @ClassRule |
| public static ClusterStartupRule cluster = new ClusterStartupRule(); |
| |
| @ClassRule |
| public static GfshCommandRule gfsh = new GfshCommandRule(); |
| |
| private static final String functionId = "genericFunctionId"; |
| private static String command = "execute function --id=" + functionId + " "; |
| |
| @BeforeClass |
| @SuppressWarnings("deprecation") |
| public static void setUpClass() throws Exception { |
| MemberVM locator = cluster.startLocatorVM(0); |
| gfsh.connectAndVerify(locator); |
| |
| 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( |
| "create region --name=regionA --type=PARTITION --group=group1 --redundant-copies=0 --total-num-buckets=2 --partition-resolver=org.apache.geode.management.internal.cli.commands.ExecuteFunctionCommandDUnitTest$MyPartitionResolver") |
| .statusIsSuccess() |
| .tableHasColumnOnlyWithValues("Member", "server-1", "server-2"); |
| |
| locator.waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + "regionA", 2); |
| |
| server1.invoke(() -> { |
| InternalCache cache = ClusterStartupRule.getCache(); |
| assertThat(cache).isNotNull(); |
| Region<String, String> region = cache.getRegion(SEPARATOR + "regionA"); |
| region.put("a", "a"); |
| region.put("b", "b"); |
| }); |
| |
| // this makes sure entry a and entry b are on different member |
| CommandResultAssert locateACommand = |
| gfsh.executeAndAssertThat("locate entry --key=a --region=" + SEPARATOR + "regionA") |
| .statusIsSuccess() |
| .hasSection("location", "data-info"); |
| locateACommand.hasDataSection().hasContent().containsEntry("Locations Found", "1"); |
| locateACommand.hasTableSection().hasColumnSize(4).hasColumn("MemberName").hasSize(1) |
| .isSubsetOf("server-1", "server-2"); |
| |
| CommandResultAssert locateBCommand = |
| gfsh.executeAndAssertThat("locate entry --key=b --region=" + SEPARATOR + "regionA") |
| .statusIsSuccess() |
| .hasSection("location", "data-info"); |
| locateBCommand.hasDataSection().hasContent().containsEntry("Locations Found", "1"); |
| locateBCommand.hasTableSection().hasColumnSize(4).hasColumn("MemberName").hasSize(1) |
| .isSubsetOf("server-1", "server-2"); |
| |
| String member1 = |
| locateACommand.getResultModel().getTableSection("location").getValue("MemberName", 0); |
| String member2 = |
| locateBCommand.getResultModel().getTableSection("location").getValue("MemberName", 0); |
| |
| assertThat(member1).isNotEqualToIgnoringCase(member2); |
| } |
| |
| @Test |
| public void noExtraArgument() { |
| TabularResultModelAssert tableAssert = |
| gfsh.executeAndAssertThat(command).statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(3) |
| .hasColumnSize(3); |
| tableAssert.hasColumn("Member").containsExactlyInAnyOrder("server-1", "server-2", "server-3"); |
| tableAssert.hasColumn("Message").containsExactlyInAnyOrder("[genericFunctionId]", |
| "[genericFunctionId]", "[genericFunctionId]"); |
| } |
| |
| @Test |
| public void withMemberOnly() { |
| gfsh.executeAndAssertThat(command + "--member=server-1").statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0).containsExactly("server-1", "OK", "[genericFunctionId]"); |
| } |
| |
| @Test |
| public void withGroupOnly() { |
| TabularResultModelAssert tableAssert = |
| gfsh.executeAndAssertThat(command + "--group=group1").statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(2) |
| .hasColumnSize(3); |
| tableAssert.hasColumn("Member").containsExactlyInAnyOrder("server-1", "server-2"); |
| tableAssert.hasColumn("Message").containsExactly("[genericFunctionId]", "[genericFunctionId]"); |
| } |
| |
| @Test |
| public void withArgumentsOnly() { |
| TabularResultModelAssert tableAssert = |
| gfsh.executeAndAssertThat(command + "--arguments=arguments").statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(3) |
| .hasColumnSize(3); |
| tableAssert.hasColumn("Member").containsExactlyInAnyOrder("server-1", "server-2", "server-3"); |
| tableAssert.hasColumn("Message").containsExactlyInAnyOrder("[genericFunctionId-arguments]", |
| "[genericFunctionId-arguments]", "[genericFunctionId-arguments]"); |
| } |
| |
| |
| @Test |
| public void withRegionOnly() { |
| // function is only executed on one member, but the returned message is repeated twice |
| // i.e. that member will execute the function on other members |
| gfsh.executeAndAssertThat(command + "--region=" + SEPARATOR + "regionA").statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", "[genericFunctionId, genericFunctionId]"); |
| } |
| |
| @Test |
| public void withRegionAndFilterMatchingMultipleMembers() { |
| // the function is executed on multiple members, the expected result is either |
| // "[genericFunctionId-a, genericFunctionId-b]" |
| // or "[genericFunctionId-b, genericFunctionId-a]" depending which server's function gets |
| // executed first |
| gfsh.executeAndAssertThat(command + "--region=" + SEPARATOR + "regionA --filter=a,b") |
| .statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", "[genericFunctionId-b, genericFunctionId-a]", |
| "[genericFunctionId-a, genericFunctionId-b]"); |
| } |
| |
| @Test |
| public void withRegionAndFilterMatchingOnlyOneMember() { |
| gfsh.executeAndAssertThat(command + "--region=" + SEPARATOR + "regionA --filter=a") |
| .statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", "[genericFunctionId-a]"); |
| } |
| |
| @Test |
| public void withRegionAndArguments() { |
| gfsh.executeAndAssertThat(command + "--region=" + SEPARATOR + "regionA --arguments=arguments") |
| .statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", |
| "[genericFunctionId-arguments, genericFunctionId-arguments]"); |
| } |
| |
| @Test |
| public void withRegionAndFilterAndArgument() { |
| gfsh.executeAndAssertThat( |
| command + "--region=" + SEPARATOR + "regionA --filter=b --arguments=arguments") |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", "[genericFunctionId-b-arguments]"); |
| } |
| |
| @Test |
| public void withRegionAndFilterAndArgumentAndResultCollector() { |
| gfsh.executeAndAssertThat( |
| command + "--region=" + SEPARATOR |
| + "regionA --filter=a --arguments=arguments --result-collector=" |
| + ToUpperResultCollector.class.getName()) |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", "[GENERICFUNCTIONID-A-ARGUMENTS]"); |
| } |
| |
| @Test |
| public void withRegionAndArgumentAndResultCollector() { |
| gfsh.executeAndAssertThat( |
| command + "--region=" + SEPARATOR + "regionA --arguments=arguments --result-collector=" |
| + ToUpperResultCollector.class.getName()) |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .isSubsetOf("server-1", "server-2", "OK", |
| "[GENERICFUNCTIONID-ARGUMENTS, GENERICFUNCTIONID-ARGUMENTS]"); |
| } |
| |
| @Test |
| public void withMemberAndArgument() { |
| gfsh.executeAndAssertThat(command + "--member=server-3 --arguments=arguments").statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .containsExactly("server-3", "OK", "[genericFunctionId-arguments]"); |
| } |
| |
| @Test |
| public void withMemberAndArgumentAndResultCollector() { |
| gfsh.executeAndAssertThat( |
| command + "--member=server-1 --arguments=arguments --result-collector=" |
| + ToUpperResultCollector.class.getName()) |
| .statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(1) |
| .hasColumnSize(3) |
| .hasRow(0) |
| .containsExactly("server-1", "OK", "[GENERICFUNCTIONID-ARGUMENTS]"); |
| } |
| |
| @Test |
| public void withArgumentAndResultCollector() { |
| |
| TabularResultModelAssert tableAssert = |
| gfsh.executeAndAssertThat(command + "--arguments=arguments --result-collector=" |
| + ToUpperResultCollector.class.getName()).statusIsSuccess() |
| .hasTableSection() |
| .hasRowSize(3) |
| .hasColumnSize(3); |
| |
| tableAssert.hasColumn("Member").containsExactlyInAnyOrder("server-1", "server-2", "server-3"); |
| tableAssert.hasColumn("Message").containsExactlyInAnyOrder("[GENERICFUNCTIONID-ARGUMENTS]", |
| "[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<String, String> { |
| @Override |
| public String getPartitionName(final EntryOperation<String, String> opDetails, |
| @Deprecated final Set<String> targetPartitions) { |
| return opDetails.getKey(); |
| } |
| |
| @Override |
| public Object getRoutingObject(final EntryOperation<String, String> opDetails) { |
| return opDetails.getKey(); |
| } |
| |
| @Override |
| public String getName() { |
| return getClass().getName(); |
| } |
| |
| @Override |
| public void close() { |
| |
| } |
| } |
| |
| public static class GenericFunctionOp implements Function<Object> { |
| private String functionId; |
| |
| GenericFunctionOp(String functionId) { |
| this.functionId = functionId; |
| } |
| |
| @Override |
| public void execute(FunctionContext<Object> context) { |
| String filter = null; |
| if (context instanceof RegionFunctionContext) { |
| RegionFunctionContext rContext = (RegionFunctionContext) context; |
| @SuppressWarnings("unchecked") |
| Set<Object> filters = (Set<Object>) rContext.getFilter(); |
| filter = Strings.join(filters, ','); |
| } |
| |
| String argument = null; |
| Object arguments = (context.getArguments()); |
| if (arguments instanceof String[]) { |
| argument = String.join(",", (String[]) arguments); |
| } |
| |
| if (filter != null && argument != null) { |
| context.getResultSender().lastResult(functionId + "-" + filter + "-" + argument); |
| } else if (filter != null) { |
| context.getResultSender().lastResult(functionId + "-" + filter); |
| } else if (argument != null) { |
| context.getResultSender().lastResult(functionId + "-" + argument); |
| } else { |
| context.getResultSender().lastResult(functionId); |
| } |
| } |
| |
| @Override |
| public String getId() { |
| return functionId; |
| } |
| } |
| |
| |
| public static class FireAndForgetFunction implements Function<Void> { |
| |
| FireAndForgetFunction() {} |
| |
| @Override |
| public String getId() { |
| return "FireAndForget"; |
| } |
| |
| @Override |
| public boolean isHA() { |
| return false; |
| } |
| |
| @Override |
| public boolean hasResult() { |
| return false; |
| } |
| |
| @Override |
| public void execute(FunctionContext<Void> context) { |
| // Do Nothing. |
| } |
| } |
| } |