| /* |
| * 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.PartitionAttributesFactory.GLOBAL_MAX_BUCKETS_DEFAULT; |
| import static org.apache.geode.cache.Region.SEPARATOR; |
| import static org.apache.geode.internal.cache.control.SerializableRestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS; |
| import static org.apache.geode.internal.cache.control.SerializableRestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS; |
| import static org.apache.geode.internal.cache.control.SerializableRestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.FULLY_SATISFIED_REDUNDANCY; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.NO_MEMBERS_FOR_REGION_HEADER; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.NO_MEMBERS_FOR_REGION_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.NO_MEMBERS_HEADER; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.NO_MEMBERS_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.PARTIALLY_SATISFIED_REDUNDANCY; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.PRIMARIES_INFO_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.SATISFIED_REDUNDANCY_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.SUMMARY_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.UNDER_REDUNDANCY_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.ZERO_REDUNDANCY_SECTION; |
| import static org.apache.geode.management.internal.cli.commands.RedundancyCommand.ZERO_REDUNDANT_COPIES; |
| import static org.apache.geode.management.internal.i18n.CliStrings.REDUNDANCY_EXCLUDE_REGION; |
| import static org.apache.geode.management.internal.i18n.CliStrings.REDUNDANCY_INCLUDE_REGION; |
| import static org.apache.geode.management.internal.i18n.CliStrings.STATUS_REDUNDANCY; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.stream.IntStream; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.cache.RegionShortcut; |
| import org.apache.geode.internal.cache.InternalCache; |
| import org.apache.geode.internal.cache.PartitionAttributesImpl; |
| import org.apache.geode.management.internal.cli.util.CommandStringBuilder; |
| 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.InfoResultModelAssert; |
| import org.apache.geode.test.junit.rules.GfshCommandRule; |
| |
| public class StatusRedundancyCommandDUnitTest { |
| @Rule |
| public ClusterStartupRule cluster = new ClusterStartupRule(); |
| |
| @Rule |
| public GfshCommandRule gfsh = new GfshCommandRule(); |
| |
| private int locatorPort; |
| private MemberVM locator; |
| private List<MemberVM> servers; |
| private static final int SERVERS_TO_START = 2; |
| private static final String SATISFIED_REGION = "satisfiedRedundancy"; |
| private static final int SATISFIABLE_COPIES = SERVERS_TO_START - 1; |
| private static final String UNSATISFIED_REGION = "unsatisfiedRedundancy"; |
| private static final int UNSATISFIABLE_COPIES = SERVERS_TO_START; |
| private static final String NO_CONFIGURED_REDUNDANCY_REGION = "noConfiguredRedundancy"; |
| private static final String ZERO_COPIES_REGION = "zeroRedundantCopies"; |
| private static final List<String> regionNames = Arrays.asList(SATISFIED_REGION, |
| UNSATISFIED_REGION, NO_CONFIGURED_REDUNDANCY_REGION, ZERO_COPIES_REGION); |
| |
| @Before |
| public void setUp() throws Exception { |
| servers = new ArrayList<>(); |
| locator = cluster.startLocatorVM(0); |
| locatorPort = locator.getPort(); |
| IntStream.range(0, SERVERS_TO_START) |
| .forEach(i -> servers.add(cluster.startServerVM(i + 1, locatorPort))); |
| gfsh.connectAndVerify(locator); |
| } |
| |
| @Test |
| public void statusRedundancyWithNoArgumentsReturnsStatusForAllRegions() { |
| createAndPopulateRegions(); |
| |
| String command = new CommandStringBuilder(STATUS_REDUNDANCY).getCommandString(); |
| |
| CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess(); |
| |
| List<String> satisfiedRegions = |
| Arrays.asList(SATISFIED_REGION, NO_CONFIGURED_REDUNDANCY_REGION); |
| |
| verifyGfshOutput(commandResult, Collections.singletonList(ZERO_COPIES_REGION), |
| Collections.singletonList(UNSATISFIED_REGION), satisfiedRegions); |
| } |
| |
| @Test |
| public void statusRedundancyWithIncludeRegionArgumentReturnsStatusForOnlyThatRegion() { |
| createAndPopulateRegions(); |
| |
| String includedRegion = SATISFIED_REGION; |
| List<String> nonIncludedRegions = new ArrayList<>(regionNames); |
| nonIncludedRegions.remove(includedRegion); |
| |
| String command = new CommandStringBuilder(STATUS_REDUNDANCY) |
| .addOption(REDUNDANCY_INCLUDE_REGION, includedRegion) |
| .getCommandString(); |
| |
| CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess() |
| .doesNotContainOutput(nonIncludedRegions.toArray(new String[0])); |
| |
| verifyGfshOutput(commandResult, new ArrayList<>(), new ArrayList<>(), |
| Collections.singletonList(includedRegion)); |
| } |
| |
| @Test |
| public void statusRedundancyWithExcludeRegionArgumentReturnsStatusForAllExceptThatRegion() { |
| createAndPopulateRegions(); |
| |
| String excludedRegion = ZERO_COPIES_REGION; |
| |
| String command = new CommandStringBuilder(STATUS_REDUNDANCY) |
| .addOption(REDUNDANCY_EXCLUDE_REGION, excludedRegion) |
| .getCommandString(); |
| |
| CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess() |
| .doesNotContainOutput(excludedRegion); |
| |
| List<String> satisfiedRegions = |
| Arrays.asList(SATISFIED_REGION, NO_CONFIGURED_REDUNDANCY_REGION); |
| |
| verifyGfshOutput(commandResult, new ArrayList<>(), |
| Collections.singletonList(UNSATISFIED_REGION), satisfiedRegions); |
| } |
| |
| @Test |
| public void statusRedundancyWithMatchingIncludeAndExcludeRegionArgumentsReturnsStatusForIncludedRegion() { |
| createAndPopulateRegions(); |
| |
| String includedAndExcludedRegion = SATISFIED_REGION; |
| List<String> nonIncludedRegions = new ArrayList<>(regionNames); |
| nonIncludedRegions.remove(includedAndExcludedRegion); |
| |
| String command = new CommandStringBuilder(STATUS_REDUNDANCY) |
| .addOption(REDUNDANCY_INCLUDE_REGION, includedAndExcludedRegion) |
| .addOption(REDUNDANCY_EXCLUDE_REGION, includedAndExcludedRegion) |
| .getCommandString(); |
| |
| CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess() |
| .doesNotContainOutput(nonIncludedRegions.toArray(new String[0])); |
| |
| verifyGfshOutput(commandResult, new ArrayList<>(), new ArrayList<>(), |
| Collections.singletonList(includedAndExcludedRegion)); |
| } |
| |
| @Test |
| public void statusRedundancyWithNoArgumentsReturnsSuccessWhenNoRegionsArePresent() { |
| String command = new CommandStringBuilder(STATUS_REDUNDANCY).getCommandString(); |
| gfsh.executeAndAssertThat(command).statusIsSuccess().hasInfoSection(NO_MEMBERS_SECTION) |
| .hasHeader() |
| .isEqualTo(NO_MEMBERS_HEADER); |
| } |
| |
| @Test |
| public void statusRedundancyWithIncludeRegionReturnsErrorWhenAtLeastOneIncludedRegionIsNotPresent() { |
| createAndPopulateRegions(); |
| |
| String nonexistentRegion = "fakeRegion"; |
| |
| String includedRegion = SATISFIED_REGION; |
| List<String> nonIncludedRegions = new ArrayList<>(regionNames); |
| nonIncludedRegions.remove(includedRegion); |
| |
| String command = new CommandStringBuilder(STATUS_REDUNDANCY) |
| .addOption(REDUNDANCY_INCLUDE_REGION, nonexistentRegion + "," + includedRegion) |
| .getCommandString(); |
| |
| CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsError() |
| .doesNotContainOutput(nonIncludedRegions.toArray(new String[0])); |
| |
| InfoResultModelAssert noMembersForRegion = |
| commandResult.hasInfoSection(NO_MEMBERS_FOR_REGION_SECTION); |
| noMembersForRegion.hasHeader().isEqualTo(NO_MEMBERS_FOR_REGION_HEADER); |
| noMembersForRegion.hasLines().containsExactly(nonexistentRegion); |
| |
| verifyGfshOutput(commandResult, new ArrayList<>(), new ArrayList<>(), |
| Collections.singletonList(includedRegion)); |
| } |
| |
| private void createAndPopulateRegions() { |
| servers.forEach(s -> s.invoke(() -> { |
| PartitionAttributesImpl attributes = new PartitionAttributesImpl(); |
| attributes.setStartupRecoveryDelay(-1); |
| attributes.setRecoveryDelay(-1); |
| |
| InternalCache cache = Objects.requireNonNull(ClusterStartupRule.getCache()); |
| |
| // Create a region whose redundancy cannot be satisfied due to not enough members |
| attributes.setRedundantCopies(UNSATISFIABLE_COPIES); |
| cache.createRegionFactory(RegionShortcut.PARTITION).setPartitionAttributes(attributes) |
| .create(UNSATISFIED_REGION); |
| |
| // Create a region whose redundancy can be satisfied |
| attributes.setRedundantCopies(SATISFIABLE_COPIES); |
| cache.createRegionFactory(RegionShortcut.PARTITION).setPartitionAttributes(attributes) |
| .create(SATISFIED_REGION); |
| |
| // Create a region configured to have zero redundant copies |
| attributes.setRedundantCopies(0); |
| cache.createRegionFactory(RegionShortcut.PARTITION).setPartitionAttributes(attributes) |
| .create(NO_CONFIGURED_REDUNDANCY_REGION); |
| })); |
| |
| servers.get(0).invoke(() -> { |
| // Create a region on only server1 so that it will not be able to create any redundant copies |
| PartitionAttributesImpl attributes = new PartitionAttributesImpl(); |
| attributes.setStartupRecoveryDelay(-1); |
| attributes.setRecoveryDelay(-1); |
| attributes.setRedundantCopies(SATISFIABLE_COPIES); |
| |
| InternalCache cache = Objects.requireNonNull(ClusterStartupRule.getCache()); |
| |
| cache.createRegionFactory(RegionShortcut.PARTITION).setPartitionAttributes(attributes).create( |
| ZERO_COPIES_REGION); |
| |
| // Populate all the regions |
| regionNames.forEach(regionName -> { |
| Region<Object, Object> region = cache.getRegion(regionName); |
| IntStream.range(0, 5 * GLOBAL_MAX_BUCKETS_DEFAULT) |
| .forEach(i -> region.put("key" + i, "value" + i)); |
| }); |
| }); |
| |
| // Wait for the regions to be ready |
| regionNames.forEach(region -> { |
| int expectedServers = region.equals(ZERO_COPIES_REGION) ? 1 : SERVERS_TO_START; |
| locator.waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + region, expectedServers); |
| }); |
| } |
| |
| private void verifyGfshOutput(CommandResultAssert result, List<String> expectedZeroCopiesRegions, |
| List<String> expectedPartiallySatisfiedRegions, List<String> expectedFullySatisfiedRegions) { |
| // Verify summary section |
| InfoResultModelAssert summary = result.hasInfoSection(SUMMARY_SECTION); |
| summary.hasOutput().contains(ZERO_REDUNDANT_COPIES + expectedZeroCopiesRegions.size()); |
| summary.hasOutput() |
| .contains(PARTIALLY_SATISFIED_REDUNDANCY + expectedPartiallySatisfiedRegions.size()); |
| summary.hasOutput().contains(FULLY_SATISFIED_REDUNDANCY + expectedFullySatisfiedRegions.size()); |
| |
| // Verify zero redundancy section |
| if (!expectedZeroCopiesRegions.isEmpty()) { |
| InfoResultModelAssert zeroRedundancy = result.hasInfoSection(ZERO_REDUNDANCY_SECTION); |
| zeroRedundancy.hasHeader().isEqualTo(NO_REDUNDANT_COPIES_FOR_REGIONS); |
| zeroRedundancy.hasOutput().contains(expectedZeroCopiesRegions); |
| } else { |
| result.hasNoSection(ZERO_REDUNDANCY_SECTION); |
| } |
| |
| // Verify under redundancy section |
| if (!expectedPartiallySatisfiedRegions.isEmpty()) { |
| InfoResultModelAssert zeroRedundancy = result.hasInfoSection(UNDER_REDUNDANCY_SECTION); |
| zeroRedundancy.hasHeader().isEqualTo(REDUNDANCY_NOT_SATISFIED_FOR_REGIONS); |
| zeroRedundancy.hasOutput().contains(expectedPartiallySatisfiedRegions); |
| } else { |
| result.hasNoSection(UNDER_REDUNDANCY_SECTION); |
| } |
| |
| // Verify fully satisfied section |
| if (!expectedFullySatisfiedRegions.isEmpty()) { |
| InfoResultModelAssert zeroRedundancy = result.hasInfoSection(SATISFIED_REDUNDANCY_SECTION); |
| zeroRedundancy.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS); |
| zeroRedundancy.hasOutput().contains(expectedFullySatisfiedRegions); |
| } else { |
| result.hasNoSection(SATISFIED_REDUNDANCY_SECTION); |
| } |
| |
| // Verify primaries section is not included |
| result.hasNoSection(PRIMARIES_INFO_SECTION); |
| } |
| } |