blob: b10db6c09137d4f98d609c1317631cbf429111bd [file] [log] [blame]
/*
* 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.
}
}
}