You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@geode.apache.org by GitBox <gi...@apache.org> on 2020/04/27 18:15:59 UTC

[GitHub] [geode] DonalEvans opened a new pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

DonalEvans opened a new pull request #5004:
URL: https://github.com/apache/geode/pull/5004


   - Add redundancyStatus method to RestoreRedundancyOperation interface
   - Add RestoreRedundancyCommand
   - Add StatusRedundancyCommand
   - Add RedundancyCommandFunction for use by the new commands
   - Create RedundancyCommandUtils to hold shared code used by both
   commands
   - Unit and DUnit tests for all the above
   
   Authored-by: Donal Evans <do...@pivotal.io>
   
   Thank you for submitting a contribution to Apache Geode.
   
   In order to streamline the review of the contribution we ask you
   to ensure the following steps have been taken:
   
   ### For all changes:
   - [x] Is there a JIRA ticket associated with this PR? Is it referenced in the commit message?
   
   - [x] Has your PR been rebased against the latest commit within the target branch (typically `develop`)?
   
   - [x] Is your initial contribution a single, squashed commit?
   
   - [x] Does `gradlew build` run cleanly?
   
   - [x] Have you written or updated unit tests to verify your changes?
   
   - [N/A] If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under [ASF 2.0](http://www.apache.org/legal/resolved.html#category-a)?
   
   ### Note:
   Please ensure that once the PR is submitted, check Concourse for build issues and
   submit an update to your PR as soon as possible. If you need help, please send an
   email to dev@geode.apache.org.
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] jujoramos commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
jujoramos commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r417251767



##########
File path: geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/RedundancyCommandFunctionTest.java
##########
@@ -0,0 +1,137 @@
+/*
+ * 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.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.OK;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+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.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.control.RestoreRedundancyOperation;
+import org.apache.geode.cache.control.RestoreRedundancyResults;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+
+public class RedundancyCommandFunctionTest {
+
+  @SuppressWarnings("unchecked")
+  private FunctionContext<Object[]> mockContext = mock(FunctionContext.class);
+  private Cache mockCache = mock(Cache.class, RETURNS_DEEP_STUBS);
+  private RestoreRedundancyOperation mockOperation =
+      mock(RestoreRedundancyOperation.class, RETURNS_DEEP_STUBS);
+  private RestoreRedundancyResults mockResults = mock(RestoreRedundancyResults.class);
+  private String message = "expected message";

Review comment:
       These attributes can all be made `final`.

##########
File path: geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommandTest.java
##########
@@ -0,0 +1,253 @@
+/*
+ * 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.management.cli.Result.Status.ERROR;
+import static org.apache.geode.management.cli.Result.Status.OK;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class StatusRedundancyCommandTest {
+
+  private StatusRedundancyCommand command;
+  private final String includeRegion1 = "include1";
+  private final String includeRegion2 = "include2";
+  private String[] includeRegions = {includeRegion1, includeRegion2};

Review comment:
       This field can be made `final`.

##########
File path: geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommandTest.java
##########
@@ -0,0 +1,253 @@
+/*
+ * 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.management.cli.Result.Status.ERROR;
+import static org.apache.geode.management.cli.Result.Status.OK;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class StatusRedundancyCommandTest {
+
+  private StatusRedundancyCommand command;
+  private final String includeRegion1 = "include1";
+  private final String includeRegion2 = "include2";
+  private String[] includeRegions = {includeRegion1, includeRegion2};
+  private final String excludeRegion1 = "exclude1";
+  private final String excludeRegion2 = "exclude2";
+  private String[] excludeRegions = {excludeRegion1, excludeRegion2};

Review comment:
       This field can be made `final`.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommand.java
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class RestoreRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "restore redundancy";
+  private static final String COMMAND_HELP =
+      "Restore redundancy and optionally reassign primary bucket hosting for partitioned regions in connected members. The default is for all regions to have redundancy restored and for primary buckets to be reassigned for better load balance.";
+  static final String INCLUDE_REGION = "include-region";
+  private static final String INCLUDE_REGION_HELP =
+      "Partitioned regions to be included when restoring redundancy. If a colocated region is included, all regions colocated with that region will also be included automatically. Includes take precedence over excludes.";
+  static final String EXCLUDE_REGION = "exclude-region";

Review comment:
       Maybe use `regions` instead of `region`?.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommand.java
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class RestoreRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "restore redundancy";
+  private static final String COMMAND_HELP =
+      "Restore redundancy and optionally reassign primary bucket hosting for partitioned regions in connected members. The default is for all regions to have redundancy restored and for primary buckets to be reassigned for better load balance.";
+  static final String INCLUDE_REGION = "include-region";

Review comment:
       Maybe use `regions` instead of `region`?.

##########
File path: geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommandDUnitTest.java
##########
@@ -0,0 +1,618 @@
+/*
+ * 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.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFERS_COMPLETED;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFER_TIME;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.FULLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PARTIALLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PRIMARIES_INFO_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SATISFIED_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SUMMARY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.UNDER_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANT_COPIES;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.COMMAND_NAME;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.EXCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.INCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.REASSIGN_PRIMARIES;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+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.Cache;
+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.internal.cache.PartitionedRegion;
+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 RestoreRedundancyCommandDUnitTest {
+  @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 = 3;
+  private static final String HIGH_REDUNDANCY_REGION_NAME = "highRedundancy";
+  private static final int HIGH_REDUNDANCY_COPIES = SERVERS_TO_START - 1;
+  private static final String LOW_REDUNDANCY_REGION_NAME = "lowRedundancy";
+  private static final String PARENT_REGION_NAME = "colocatedParent";
+  private static final String CHILD_REGION_NAME = "colocatedChild";
+  private static final int SINGLE_REDUNDANT_COPY = 1;
+  private static final String ZERO_REDUNDANCY_REGION_NAME = "zeroRedundancy";
+
+  @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 restoreRedundancyWithNoArgumentsRestoresRedundancyForAllRegions() {
+    List<String> regionNames = getAllRegionNames();
+    createAndPopulateRegions(regionNames);
+
+    int numberOfServers = servers.size();
+    regionNames.forEach(region -> locator
+        .waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + region, numberOfServers));
+
+    String command = new CommandStringBuilder(COMMAND_NAME).getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .hasNoSection(UNDER_REDUNDANCY_SECTION);
+
+    InfoResultModelAssert summary = commandResult.hasInfoSection(SUMMARY_SECTION);
+    summary.hasOutput().contains(ZERO_REDUNDANT_COPIES + 0);
+    summary.hasOutput().contains(PARTIALLY_SATISFIED_REDUNDANCY + 0);
+    summary.hasOutput().contains(FULLY_SATISFIED_REDUNDANCY + regionNames.size());
+
+    InfoResultModelAssert satisfiedSection =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedSection.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedSection.hasOutput().contains(regionNames);

Review comment:
       This code block is duplicated within the test, it can be extracted to a separate method.

##########
File path: geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommandDUnitTest.java
##########
@@ -0,0 +1,618 @@
+/*
+ * 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.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFERS_COMPLETED;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFER_TIME;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.FULLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PARTIALLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PRIMARIES_INFO_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SATISFIED_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SUMMARY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.UNDER_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANT_COPIES;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.COMMAND_NAME;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.EXCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.INCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.REASSIGN_PRIMARIES;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+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.Cache;
+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.internal.cache.PartitionedRegion;
+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 RestoreRedundancyCommandDUnitTest {
+  @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 = 3;
+  private static final String HIGH_REDUNDANCY_REGION_NAME = "highRedundancy";
+  private static final int HIGH_REDUNDANCY_COPIES = SERVERS_TO_START - 1;
+  private static final String LOW_REDUNDANCY_REGION_NAME = "lowRedundancy";
+  private static final String PARENT_REGION_NAME = "colocatedParent";
+  private static final String CHILD_REGION_NAME = "colocatedChild";
+  private static final int SINGLE_REDUNDANT_COPY = 1;
+  private static final String ZERO_REDUNDANCY_REGION_NAME = "zeroRedundancy";
+
+  @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 restoreRedundancyWithNoArgumentsRestoresRedundancyForAllRegions() {
+    List<String> regionNames = getAllRegionNames();
+    createAndPopulateRegions(regionNames);
+
+    int numberOfServers = servers.size();
+    regionNames.forEach(region -> locator
+        .waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + region, numberOfServers));
+
+    String command = new CommandStringBuilder(COMMAND_NAME).getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .hasNoSection(UNDER_REDUNDANCY_SECTION);
+
+    InfoResultModelAssert summary = commandResult.hasInfoSection(SUMMARY_SECTION);
+    summary.hasOutput().contains(ZERO_REDUNDANT_COPIES + 0);
+    summary.hasOutput().contains(PARTIALLY_SATISFIED_REDUNDANCY + 0);
+    summary.hasOutput().contains(FULLY_SATISFIED_REDUNDANCY + regionNames.size());
+
+    InfoResultModelAssert satisfiedSection =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedSection.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedSection.hasOutput().contains(regionNames);
+
+    InfoResultModelAssert primariesSection = commandResult.hasInfoSection(PRIMARIES_INFO_SECTION);
+    primariesSection.hasOutput().contains(PRIMARY_TRANSFERS_COMPLETED, PRIMARY_TRANSFER_TIME);
+
+    // Confirm all regions have their configured redundancy and that primaries were balanced
+    int numberOfActiveServers = servers.size();
+    servers.get(0).invoke(() -> {
+      for (String regionName : regionNames) {
+        assertRedundancyStatusForRegion(regionName, true);
+        assertPrimariesBalanced(regionName, numberOfActiveServers, true);
+      }
+    });
+  }
+
+  @Test
+  public void restoreRedundancyWithIncludeRegionArgumentRestoresOnlyThoseRegions() {
+    List<String> regionNames = getAllRegionNames();
+    createAndPopulateRegions(regionNames);
+
+    int numberOfServers = servers.size();
+    regionNames.forEach(region -> locator
+        .waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + region, numberOfServers));
+
+    String regionToInclude = HIGH_REDUNDANCY_REGION_NAME;
+    List<String> nonIncludedRegions = new ArrayList<>(regionNames);
+    nonIncludedRegions.remove(regionToInclude);
+
+    String command = new CommandStringBuilder(COMMAND_NAME)
+        .addOption(INCLUDE_REGION, regionToInclude).getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .hasNoSection(UNDER_REDUNDANCY_SECTION)
+        .doesNotContainOutput(nonIncludedRegions.toArray(new String[0]));
+
+    InfoResultModelAssert summary = commandResult.hasInfoSection(SUMMARY_SECTION);
+    summary.hasOutput().contains(ZERO_REDUNDANT_COPIES + 0);
+    summary.hasOutput().contains(PARTIALLY_SATISFIED_REDUNDANCY + 0);
+    summary.hasOutput().contains(FULLY_SATISFIED_REDUNDANCY + 1);
+
+    InfoResultModelAssert satisfiedSection =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedSection.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedSection.hasOutput().contains(regionToInclude);

Review comment:
       This code block is duplicated within the test, it can be extracted to a separate method.

##########
File path: geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommandDUnitTest.java
##########
@@ -0,0 +1,618 @@
+/*
+ * 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.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFERS_COMPLETED;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFER_TIME;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.FULLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PARTIALLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PRIMARIES_INFO_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SATISFIED_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SUMMARY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.UNDER_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANT_COPIES;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.COMMAND_NAME;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.EXCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.INCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.RestoreRedundancyCommand.REASSIGN_PRIMARIES;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+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.Cache;
+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.internal.cache.PartitionedRegion;
+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 RestoreRedundancyCommandDUnitTest {
+  @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 = 3;
+  private static final String HIGH_REDUNDANCY_REGION_NAME = "highRedundancy";
+  private static final int HIGH_REDUNDANCY_COPIES = SERVERS_TO_START - 1;
+  private static final String LOW_REDUNDANCY_REGION_NAME = "lowRedundancy";
+  private static final String PARENT_REGION_NAME = "colocatedParent";
+  private static final String CHILD_REGION_NAME = "colocatedChild";
+  private static final int SINGLE_REDUNDANT_COPY = 1;
+  private static final String ZERO_REDUNDANCY_REGION_NAME = "zeroRedundancy";
+
+  @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 restoreRedundancyWithNoArgumentsRestoresRedundancyForAllRegions() {
+    List<String> regionNames = getAllRegionNames();
+    createAndPopulateRegions(regionNames);
+
+    int numberOfServers = servers.size();
+    regionNames.forEach(region -> locator
+        .waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + region, numberOfServers));
+
+    String command = new CommandStringBuilder(COMMAND_NAME).getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command).statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .hasNoSection(UNDER_REDUNDANCY_SECTION);
+
+    InfoResultModelAssert summary = commandResult.hasInfoSection(SUMMARY_SECTION);
+    summary.hasOutput().contains(ZERO_REDUNDANT_COPIES + 0);
+    summary.hasOutput().contains(PARTIALLY_SATISFIED_REDUNDANCY + 0);
+    summary.hasOutput().contains(FULLY_SATISFIED_REDUNDANCY + regionNames.size());
+
+    InfoResultModelAssert satisfiedSection =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedSection.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedSection.hasOutput().contains(regionNames);
+
+    InfoResultModelAssert primariesSection = commandResult.hasInfoSection(PRIMARIES_INFO_SECTION);
+    primariesSection.hasOutput().contains(PRIMARY_TRANSFERS_COMPLETED, PRIMARY_TRANSFER_TIME);
+
+    // Confirm all regions have their configured redundancy and that primaries were balanced
+    int numberOfActiveServers = servers.size();
+    servers.get(0).invoke(() -> {
+      for (String regionName : regionNames) {
+        assertRedundancyStatusForRegion(regionName, true);
+        assertPrimariesBalanced(regionName, numberOfActiveServers, true);
+      }
+    });
+  }
+
+  @Test
+  public void restoreRedundancyWithIncludeRegionArgumentRestoresOnlyThoseRegions() {
+    List<String> regionNames = getAllRegionNames();
+    createAndPopulateRegions(regionNames);
+
+    int numberOfServers = servers.size();
+    regionNames.forEach(region -> locator
+        .waitUntilRegionIsReadyOnExactlyThisManyServers(SEPARATOR + region, numberOfServers));
+
+    String regionToInclude = HIGH_REDUNDANCY_REGION_NAME;
+    List<String> nonIncludedRegions = new ArrayList<>(regionNames);
+    nonIncludedRegions.remove(regionToInclude);
+
+    String command = new CommandStringBuilder(COMMAND_NAME)
+        .addOption(INCLUDE_REGION, regionToInclude).getCommandString();

Review comment:
       This code block is duplicated within the test, it can be extracted to a separate method.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommand.java
##########
@@ -0,0 +1,121 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class StatusRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "status redundancy";
+  private static final String COMMAND_HELP =
+      "Report the redundancy status for partitioned regions in connected members. The default is to report status for all regions.";
+  static final String INCLUDE_REGION = "include-region";

Review comment:
       Maybe use `regions` instead of `region`?.
   
   

##########
File path: geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommandDUnitTest.java
##########
@@ -0,0 +1,281 @@
+/*
+ * 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.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.FULLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PARTIALLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SATISFIED_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SUMMARY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.UNDER_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANT_COPIES;
+import static org.apache.geode.management.internal.cli.commands.StatusRedundancyCommand.COMMAND_NAME;
+import static org.apache.geode.management.internal.cli.commands.StatusRedundancyCommand.EXCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.StatusRedundancyCommand.INCLUDE_REGION;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+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(COMMAND_NAME).getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command);
+    commandResult.statusIsSuccess();
+
+    verifySummarySection(commandResult, 1, 1, 2);
+
+    InfoResultModelAssert zeroRedundancy = commandResult.hasInfoSection(ZERO_REDUNDANCY_SECTION);
+    zeroRedundancy.hasHeader().isEqualTo(NO_REDUNDANT_COPIES_FOR_REGIONS);
+    zeroRedundancy.hasOutput().contains(ZERO_COPIES_REGION);
+
+    InfoResultModelAssert underRedundancy = commandResult.hasInfoSection(UNDER_REDUNDANCY_SECTION);
+    underRedundancy.hasHeader().isEqualTo(REDUNDANCY_NOT_SATISFIED_FOR_REGIONS);
+    underRedundancy.hasOutput().contains(UNSATISFIED_REGION);
+
+    InfoResultModelAssert satisfiedRedundancy =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedRedundancy.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedRedundancy.hasOutput().contains(SATISFIED_REGION,
+        NO_CONFIGURED_REDUNDANCY_REGION);
+  }
+
+  @Test
+  public void statusRedundancyWithIncludeRegionArgumentReturnsStatusForOnlyThatRegion() {
+    createAndPopulateRegions();
+
+    String command = new CommandStringBuilder(COMMAND_NAME)
+        .addOption(INCLUDE_REGION, SATISFIED_REGION)
+        .getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command);
+    commandResult.statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .hasNoSection(UNDER_REDUNDANCY_SECTION)
+        .doesNotContainOutput(UNSATISFIED_REGION)
+        .doesNotContainOutput(ZERO_COPIES_REGION)
+        .doesNotContainOutput(NO_CONFIGURED_REDUNDANCY_REGION);
+
+    verifySummarySection(commandResult, 0, 0, 1);
+
+    InfoResultModelAssert satisfiedRedundancy =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedRedundancy.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedRedundancy.hasOutput().contains(SATISFIED_REGION);

Review comment:
       This code block is duplicated within the test, it can be extracted to a separate method.

##########
File path: geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommandTest.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.management.cli.Result.Status.ERROR;
+import static org.apache.geode.management.cli.Result.Status.OK;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class RestoreRedundancyCommandTest {
+
+  private RestoreRedundancyCommand command;
+  private final String includeRegion1 = "include1";
+  private final String includeRegion2 = "include2";
+  private String[] includeRegions = {includeRegion1, includeRegion2};
+  private final String excludeRegion1 = "exclude1";
+  private final String excludeRegion2 = "exclude2";
+  private String[] excludeRegions = {excludeRegion1, excludeRegion2};
+  private final boolean shouldReassignPrimaries = true;
+  private DistributedMember mockMember1;
+  private DistributedMember mockMember2;
+  private CliFunctionResult successFunctionResult;
+  private CliFunctionResult errorFunctionResult;
+  private RedundancyCommandUtils mockUtils;
+  private Object[] expectedArguments;
+
+  @Before
+  public void setUp() {
+    mockMember1 = mock(DistributedMember.class);
+    mockMember2 = mock(DistributedMember.class);
+    expectedArguments = new Object[] {includeRegions, excludeRegions, shouldReassignPrimaries};
+
+    successFunctionResult = mock(CliFunctionResult.class);
+    when(successFunctionResult.getStatus()).thenReturn(OK.name());
+
+    errorFunctionResult = mock(CliFunctionResult.class);
+    when(errorFunctionResult.getStatus()).thenReturn(ERROR.name());
+
+    mockUtils = mock(RedundancyCommandUtils.class);
+    Cache mockCache = mock(InternalCache.class);
+
+    command = spy(new RestoreRedundancyCommand());
+    doReturn(mockUtils).when(command).getUtils();
+    doReturn(mockCache).when(command).getCache();
+  }
+
+  @Test
+  public void executeCallsGetNoViableMembersResultWhenNoViableMembersAreFoundForAtLeastOneRegion() {
+    RebalanceOperationPerformer.MemberPRInfo firstMemberPRInfo =
+        new RebalanceOperationPerformer.MemberPRInfo();
+    String region1 = "region1";
+    firstMemberPRInfo.region = region1;
+    RebalanceOperationPerformer.MemberPRInfo secondMemberPRInfo =
+        new RebalanceOperationPerformer.MemberPRInfo();
+    String region2 = "region2";
+    secondMemberPRInfo.region = region2;
+
+    // Put something in the members list when we call populateLists
+    doAnswer(invocation -> {
+      List<RebalanceOperationPerformer.MemberPRInfo> memberPRInfoList = invocation.getArgument(0);
+      memberPRInfoList.add(firstMemberPRInfo);
+      memberPRInfoList.add(secondMemberPRInfo);
+      return null;
+    }).when(mockUtils).populateLists(any(), any(), any(), any(), any());
+
+    List<DistributedMember> viableMembers = new ArrayList<>();
+    viableMembers.add(mock(DistributedMember.class));
+
+    when(mockUtils.filterViableMembersForVersion(eq(firstMemberPRInfo), any()))
+        .thenReturn(viableMembers);
+    when(mockUtils.filterViableMembersForVersion(eq(secondMemberPRInfo), any()))
+        .thenReturn(Collections.emptyList());
+
+    command.execute(includeRegions, excludeRegions, shouldReassignPrimaries);
+
+    verify(mockUtils, times(1)).getNoViableMembersResult(any(), eq(region2));
+    verify(mockUtils, times(0)).getNoViableMembersResult(any(), eq(region1));
+  }
+
+  @Test
+  public void executeCallsMethodsWithCorrectArgumentsWhenViableMembersAreFoundForAllRegions() {
+    RebalanceOperationPerformer.MemberPRInfo firstMemberPRInfo =
+        mock(RebalanceOperationPerformer.MemberPRInfo.class);
+    RebalanceOperationPerformer.MemberPRInfo secondMemberPRInfo =
+        mock(RebalanceOperationPerformer.MemberPRInfo.class);
+    List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion = new ArrayList<>();
+    membersForEachRegion.add(firstMemberPRInfo);
+    membersForEachRegion.add(secondMemberPRInfo);
+
+    // Put something in the members list when we call populateLists
+    doAnswer(invocation -> {
+      List<RebalanceOperationPerformer.MemberPRInfo> memberPRInfoList = invocation.getArgument(0);
+      memberPRInfoList.add(firstMemberPRInfo);
+      memberPRInfoList.add(secondMemberPRInfo);
+      return null;
+    }).when(mockUtils).populateLists(any(), any(), any(), any(), any());
+
+    // Allow us to get past the filter viable members step
+    List<DistributedMember> viableMembers = new ArrayList<>();
+    viableMembers.add(mock(DistributedMember.class));
+    when(mockUtils.filterViableMembersForVersion(any(), any())).thenReturn(viableMembers);
+
+    // Prevent the executeFunctionOnMembers method from doing anything
+    List<CliFunctionResult> returnedList = new ArrayList<>();
+    doReturn(returnedList).when(command).executeFunctionOnMembers(any(), any(), anyBoolean(),
+        any());
+
+    command.execute(includeRegions, excludeRegions, shouldReassignPrimaries);
+
+    // Confirm we set the correct viable members on the memberPRInfos
+    verify(mockUtils, times(1)).filterViableMembersForVersion(firstMemberPRInfo,
+        REDUNDANCY_COMMAND_ADDED_VERSION);
+    verify(mockUtils, times(1)).filterViableMembersForVersion(secondMemberPRInfo,
+        REDUNDANCY_COMMAND_ADDED_VERSION);
+    assertThat(firstMemberPRInfo.dsMemberList, is(viableMembers));
+    assertThat(secondMemberPRInfo.dsMemberList, is(viableMembers));
+
+    // Confirm we called executeFunctionOnMembers and buildResultModelFromFunctionResults with the
+    // correct arguments
+    verify(command, times(1)).executeFunctionOnMembers(includeRegions, excludeRegions,
+        shouldReassignPrimaries, membersForEachRegion);
+    boolean isStatusCommand = false;

Review comment:
       No need for this variable.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RedundancyCommandUtils.java
##########
@@ -0,0 +1,269 @@
+/*
+ * 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.internal.cache.control.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFERS_COMPLETED;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFER_TIME;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.geode.cache.control.RegionRedundancyStatus;
+import org.apache.geode.cache.control.RestoreRedundancyResults;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl;
+import org.apache.geode.internal.serialization.Version;
+import org.apache.geode.management.ManagementService;
+import org.apache.geode.management.cli.Result;
+import org.apache.geode.management.internal.cli.result.model.InfoResultModel;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class RedundancyCommandUtils {
+  public static final String NO_MEMBERS_WITH_VERSION_FOR_REGION =
+      "No members with a version greater than or equal to %s were found for region %s";
+  public static final String NO_MEMBERS_SECTION = "no-members";
+  public static final String NO_MEMBERS_HEADER =
+      "No partitioned regions were found.";
+  public static final String NO_MEMBERS_FOR_REGION_SECTION = "no-members-for-region";
+  public static final String NO_MEMBERS_FOR_REGION_HEADER =
+      "No members hosting the following regions were found: ";
+  public static final String ERROR_SECTION = "errors";
+  public static final String ERROR_SECTION_HEADER =
+      "The following errors or exceptions were encountered: ";
+  public static final String SUMMARY_SECTION = "summary-section";
+  public static final String ZERO_REDUNDANT_COPIES =
+      "Number of regions with zero redundant copies = ";
+  public static final String PARTIALLY_SATISFIED_REDUNDANCY =
+      "Number of regions with partially satisfied redundancy = ";
+  public static final String FULLY_SATISFIED_REDUNDANCY =
+      "Number of regions with fully satisfied redundancy = ";
+  public static final String ZERO_REDUNDANCY_SECTION = "zero-redundancy";
+  public static final String UNDER_REDUNDANCY_SECTION = "under-redundancy";
+  public static final String SATISFIED_REDUNDANCY_SECTION = "satisfied-redundancy";
+  public static final String PRIMARIES_INFO_SECTION = "primaries-info";
+  public static final String EXCEPTION_MEMBER_MESSAGE = "Exception occurred on member %s: %s";
+  public static final Version REDUNDANCY_COMMAND_ADDED_VERSION = Version.GEODE_1_13_0;
+  public static final String INDENT = "  ";
+
+  void populateLists(List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion,
+      List<String> noMemberRegions, String[] includeRegions, String[] excludeRegions,
+      InternalCache cache) {
+    // Include all regions
+    if (includeRegions == null) {
+      // Exclude these regions
+      List<String> excludedRegionList =
+          excludeRegions != null ? Arrays.asList(excludeRegions) : new ArrayList<>();
+
+      List<RebalanceOperationPerformer.MemberPRInfo> memberRegionList =
+          getMembersForEachRegion(excludedRegionList, cache);
+      membersForEachRegion.addAll(memberRegionList);
+    } else {
+      for (String regionName : includeRegions) {
+        DistributedMember memberForRegion = getOneMemberForRegion(regionName, cache);
+
+        // If we did not find a member for this region name, add it to the list of regions with no
+        // members
+        if (memberForRegion == null) {
+          noMemberRegions.add(regionName);
+        } else {
+          RebalanceOperationPerformer.MemberPRInfo memberPRInfo =
+              new RebalanceOperationPerformer.MemberPRInfo();
+          memberPRInfo.region = regionName;
+          memberPRInfo.dsMemberList.add(memberForRegion);
+          membersForEachRegion.add(memberPRInfo);
+        }
+      }
+    }
+  }
+
+  // Extracted for testing
+  List<RebalanceOperationPerformer.MemberPRInfo> getMembersForEachRegion(
+      List<String> excludedRegionList, InternalCache cache) {
+    return RebalanceOperationPerformer.getMemberRegionList(
+        ManagementService.getManagementService(cache), cache, excludedRegionList);
+  }
+
+  // Extracted for testing
+  DistributedMember getOneMemberForRegion(String regionName, InternalCache cache) {
+    String regionNameWithSeparator = regionName;
+    // The getAssociatedMembers method requires region names start with '/'
+    if (!regionName.startsWith("/")) {
+      regionNameWithSeparator = "/" + regionName;

Review comment:
       Maybe use `CacheConfig.SEPARATOR` instead of hardcoding the character? (I know we don't it consistently across the code, but...).

##########
File path: geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommandDUnitTest.java
##########
@@ -0,0 +1,281 @@
+/*
+ * 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.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.FULLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_FOR_REGION_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_HEADER;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.NO_MEMBERS_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.PARTIALLY_SATISFIED_REDUNDANCY;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SATISFIED_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.SUMMARY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.UNDER_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANCY_SECTION;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.ZERO_REDUNDANT_COPIES;
+import static org.apache.geode.management.internal.cli.commands.StatusRedundancyCommand.COMMAND_NAME;
+import static org.apache.geode.management.internal.cli.commands.StatusRedundancyCommand.EXCLUDE_REGION;
+import static org.apache.geode.management.internal.cli.commands.StatusRedundancyCommand.INCLUDE_REGION;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+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(COMMAND_NAME).getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command);
+    commandResult.statusIsSuccess();
+
+    verifySummarySection(commandResult, 1, 1, 2);
+
+    InfoResultModelAssert zeroRedundancy = commandResult.hasInfoSection(ZERO_REDUNDANCY_SECTION);
+    zeroRedundancy.hasHeader().isEqualTo(NO_REDUNDANT_COPIES_FOR_REGIONS);
+    zeroRedundancy.hasOutput().contains(ZERO_COPIES_REGION);
+
+    InfoResultModelAssert underRedundancy = commandResult.hasInfoSection(UNDER_REDUNDANCY_SECTION);
+    underRedundancy.hasHeader().isEqualTo(REDUNDANCY_NOT_SATISFIED_FOR_REGIONS);
+    underRedundancy.hasOutput().contains(UNSATISFIED_REGION);
+
+    InfoResultModelAssert satisfiedRedundancy =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedRedundancy.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedRedundancy.hasOutput().contains(SATISFIED_REGION,
+        NO_CONFIGURED_REDUNDANCY_REGION);
+  }
+
+  @Test
+  public void statusRedundancyWithIncludeRegionArgumentReturnsStatusForOnlyThatRegion() {
+    createAndPopulateRegions();
+
+    String command = new CommandStringBuilder(COMMAND_NAME)
+        .addOption(INCLUDE_REGION, SATISFIED_REGION)
+        .getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command);
+    commandResult.statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .hasNoSection(UNDER_REDUNDANCY_SECTION)
+        .doesNotContainOutput(UNSATISFIED_REGION)
+        .doesNotContainOutput(ZERO_COPIES_REGION)
+        .doesNotContainOutput(NO_CONFIGURED_REDUNDANCY_REGION);
+
+    verifySummarySection(commandResult, 0, 0, 1);
+
+    InfoResultModelAssert satisfiedRedundancy =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedRedundancy.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedRedundancy.hasOutput().contains(SATISFIED_REGION);
+  }
+
+  @Test
+  public void statusRedundancyWithExcludeRegionArgumentReturnsStatusForAllExceptThatRegion() {
+    createAndPopulateRegions();
+
+    String command = new CommandStringBuilder(COMMAND_NAME)
+        .addOption(EXCLUDE_REGION, ZERO_COPIES_REGION)
+        .getCommandString();
+
+    CommandResultAssert commandResult = gfsh.executeAndAssertThat(command);
+    commandResult.statusIsSuccess()
+        .hasNoSection(ZERO_REDUNDANCY_SECTION)
+        .doesNotContainOutput(ZERO_COPIES_REGION);
+
+    verifySummarySection(commandResult, 0, 1, 2);
+
+    InfoResultModelAssert underRedundancy = commandResult.hasInfoSection(UNDER_REDUNDANCY_SECTION);
+    underRedundancy.hasHeader().isEqualTo(REDUNDANCY_NOT_SATISFIED_FOR_REGIONS);
+    underRedundancy.hasOutput().contains(UNSATISFIED_REGION);
+
+    InfoResultModelAssert satisfiedRedundancy =
+        commandResult.hasInfoSection(SATISFIED_REDUNDANCY_SECTION);
+    satisfiedRedundancy.hasHeader().isEqualTo(REDUNDANCY_SATISFIED_FOR_REGIONS);
+    satisfiedRedundancy.hasOutput().contains(SATISFIED_REGION,
+        NO_CONFIGURED_REDUNDANCY_REGION);

Review comment:
       This code block is duplicated within the test, it can be extracted to a separate method.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommand.java
##########
@@ -0,0 +1,121 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class StatusRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "status redundancy";
+  private static final String COMMAND_HELP =
+      "Report the redundancy status for partitioned regions in connected members. The default is to report status for all regions.";
+  static final String INCLUDE_REGION = "include-region";
+  private static final String INCLUDE_REGION_HELP =
+      "Partitioned regions to be included when reporting redundancy status. Includes take precedence over excludes.";
+  static final String EXCLUDE_REGION = "exclude-region";
+  private static final String EXCLUDE_REGION_HELP =
+      "Partitioned regions to be excluded when reporting redundancy status.";
+
+  @CliCommand(value = COMMAND_NAME, help = COMMAND_HELP)
+  @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_DATA, CliStrings.TOPIC_GEODE_REGION})
+  @ResourceOperation(resource = ResourcePermission.Resource.DATA,
+      operation = ResourcePermission.Operation.READ)
+  public ResultModel execute(
+      @CliOption(key = INCLUDE_REGION, help = INCLUDE_REGION_HELP) String[] includeRegions,
+      @CliOption(key = EXCLUDE_REGION, help = EXCLUDE_REGION_HELP) String[] excludeRegions) {
+    RedundancyCommandUtils utils = getUtils();
+
+    List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion = new ArrayList<>();
+    List<String> includedRegionsWithNoMembers = new ArrayList<>();
+
+    utils.populateLists(membersForEachRegion, includedRegionsWithNoMembers, includeRegions,
+        excludeRegions, (InternalCache) getCache());
+
+    for (RebalanceOperationPerformer.MemberPRInfo prInfo : membersForEachRegion) {
+      // Filter out any members using older versions of Geode
+      List<DistributedMember> viableMembers =
+          utils.filterViableMembersForVersion(prInfo, REDUNDANCY_COMMAND_ADDED_VERSION);
+
+      if (viableMembers.size() == 0) {
+        // If no viable members were found, return with error status
+        return utils.getNoViableMembersResult(REDUNDANCY_COMMAND_ADDED_VERSION, prInfo.region);
+      } else {
+        // Update the MemberPRInfo with the viable members
+        prInfo.dsMemberList = viableMembers;
+      }
+    }
+
+    List<CliFunctionResult> functionResults =
+        executeFunctionOnMembers(includeRegions, excludeRegions, membersForEachRegion);
+
+    return utils.buildResultModelFromFunctionResults(functionResults, includedRegionsWithNoMembers,
+        true);
+  }
+
+  List<CliFunctionResult> executeFunctionOnMembers(String[] includeRegions, String[] excludeRegions,
+      List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion) {
+
+    List<CliFunctionResult> functionResults = new ArrayList<>();
+    boolean shouldNotReassignPrimaries = false;

Review comment:
       Unneeded variable.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommand.java
##########
@@ -0,0 +1,121 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class StatusRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "status redundancy";
+  private static final String COMMAND_HELP =
+      "Report the redundancy status for partitioned regions in connected members. The default is to report status for all regions.";
+  static final String INCLUDE_REGION = "include-region";
+  private static final String INCLUDE_REGION_HELP =
+      "Partitioned regions to be included when reporting redundancy status. Includes take precedence over excludes.";
+  static final String EXCLUDE_REGION = "exclude-region";

Review comment:
       Maybe use `regions` instead of `region`?.
   
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] lgtm-com[bot] commented on pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
lgtm-com[bot] commented on pull request #5004:
URL: https://github.com/apache/geode/pull/5004#issuecomment-620315436


   This pull request **introduces 2 alerts** when merging 4426e0621a2f3a3778fe0ec5432f13dd89614ac1 into 6d358846566fd74ee85ecab311d2eeb004c44897 - [view on LGTM.com](https://lgtm.com/projects/g/apache/geode/rev/pr-22aebb54a0f8dd3dc909416cc5650c3281d317ba)
   
   **new alerts:**
   
   * 2 for Cast from abstract to concrete collection


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r416133248



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommand.java
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.SingleGfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class RestoreRedundancyCommand extends SingleGfshCommand {

Review comment:
       Thanks for catching this.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] jinmeiliao commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
jinmeiliao commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r416090678



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/RedundancyCommandFunction.java
##########
@@ -0,0 +1,78 @@
+/*
+ * 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.apache.geode.cache.control.RestoreRedundancyResults.Status.ERROR;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.geode.cache.control.RestoreRedundancyOperation;
+import org.apache.geode.cache.control.RestoreRedundancyResults;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.management.cli.CliFunction;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+
+public class RedundancyCommandFunction extends CliFunction<Object[]> {
+  private static final long serialVersionUID = 5633636343813884996L;
+
+  @Override
+  public CliFunctionResult executeFunction(FunctionContext<Object[]> context) {
+    Object[] arguments = context.getArguments();
+    String[] includeRegions = (String[]) arguments[0];
+    Set<String> includeRegionsSet = null;
+    if (includeRegions != null) {
+      includeRegionsSet = new HashSet<>(Arrays.asList(includeRegions));
+    }
+
+    String[] excludeRegions = (String[]) arguments[1];
+    Set<String> excludeRegionsSet = null;
+    if (excludeRegions != null) {
+      excludeRegionsSet = new HashSet<>(Arrays.asList(excludeRegions));
+    }
+
+    boolean shouldReassignPrimaries = (boolean) arguments[2];
+
+    boolean isStatusCommand = false;
+    if (arguments.length > 3) {
+      isStatusCommand = (boolean) arguments[3];
+    }
+
+    RestoreRedundancyResults results;
+    try {
+      RestoreRedundancyOperation redundancyOperation =
+          context.getCache().getResourceManager().createRestoreRedundancyOperation();
+      redundancyOperation.includeRegions(includeRegionsSet);
+      redundancyOperation.excludeRegions(excludeRegionsSet);
+      if (isStatusCommand) {
+        results = redundancyOperation.redundancyStatus();
+      } else {
+        redundancyOperation.shouldReassignPrimaries(shouldReassignPrimaries);
+        results = redundancyOperation.start().get();
+      }
+    } catch (Exception ex) {

Review comment:
       consider get rid of the try/catch block, the CliFunction already handles any exception thrown by the child in a uniform way.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommand.java
##########
@@ -0,0 +1,121 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.SingleGfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class StatusRedundancyCommand extends SingleGfshCommand {

Review comment:
       same here, extends GfshCommand should be enough here.

##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommand.java
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.SingleGfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class RestoreRedundancyCommand extends SingleGfshCommand {

Review comment:
       since this command doesn't affect cluster configuration at all. I would just have this command extend GfshCommand. SingleGfshCommand is used by command that needs to change the cluster configuration.

##########
File path: geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommandTest.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.management.cli.Result.Status.ERROR;
+import static org.apache.geode.management.cli.Result.Status.OK;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class RestoreRedundancyCommandTest {

Review comment:
       If you want to truly test the command like how it's invoked using the gfsh command and examine the result, you can use the `GfshParserRule` and create a spy of the command class to interact with it. See `CreateRegionCommandTest` as an example. 




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r418172673



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommand.java
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class RestoreRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "restore redundancy";
+  private static final String COMMAND_HELP =
+      "Restore redundancy and optionally reassign primary bucket hosting for partitioned regions in connected members. The default is for all regions to have redundancy restored and for primary buckets to be reassigned for better load balance.";
+  static final String INCLUDE_REGION = "include-region";

Review comment:
       The existing `rebalance` command uses `--include-region` and `--exclude-region`, so that naming was kept here for consistency.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r418223262



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RedundancyCommandUtils.java
##########
@@ -0,0 +1,269 @@
+/*
+ * 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.internal.cache.control.RestoreRedundancyResultsImpl.NO_REDUNDANT_COPIES_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFERS_COMPLETED;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.PRIMARY_TRANSFER_TIME;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_NOT_SATISFIED_FOR_REGIONS;
+import static org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl.REDUNDANCY_SATISFIED_FOR_REGIONS;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.geode.cache.control.RegionRedundancyStatus;
+import org.apache.geode.cache.control.RestoreRedundancyResults;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.cache.control.RestoreRedundancyResultsImpl;
+import org.apache.geode.internal.serialization.Version;
+import org.apache.geode.management.ManagementService;
+import org.apache.geode.management.cli.Result;
+import org.apache.geode.management.internal.cli.result.model.InfoResultModel;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class RedundancyCommandUtils {
+  public static final String NO_MEMBERS_WITH_VERSION_FOR_REGION =
+      "No members with a version greater than or equal to %s were found for region %s";
+  public static final String NO_MEMBERS_SECTION = "no-members";
+  public static final String NO_MEMBERS_HEADER =
+      "No partitioned regions were found.";
+  public static final String NO_MEMBERS_FOR_REGION_SECTION = "no-members-for-region";
+  public static final String NO_MEMBERS_FOR_REGION_HEADER =
+      "No members hosting the following regions were found: ";
+  public static final String ERROR_SECTION = "errors";
+  public static final String ERROR_SECTION_HEADER =
+      "The following errors or exceptions were encountered: ";
+  public static final String SUMMARY_SECTION = "summary-section";
+  public static final String ZERO_REDUNDANT_COPIES =
+      "Number of regions with zero redundant copies = ";
+  public static final String PARTIALLY_SATISFIED_REDUNDANCY =
+      "Number of regions with partially satisfied redundancy = ";
+  public static final String FULLY_SATISFIED_REDUNDANCY =
+      "Number of regions with fully satisfied redundancy = ";
+  public static final String ZERO_REDUNDANCY_SECTION = "zero-redundancy";
+  public static final String UNDER_REDUNDANCY_SECTION = "under-redundancy";
+  public static final String SATISFIED_REDUNDANCY_SECTION = "satisfied-redundancy";
+  public static final String PRIMARIES_INFO_SECTION = "primaries-info";
+  public static final String EXCEPTION_MEMBER_MESSAGE = "Exception occurred on member %s: %s";
+  public static final Version REDUNDANCY_COMMAND_ADDED_VERSION = Version.GEODE_1_13_0;
+  public static final String INDENT = "  ";
+
+  void populateLists(List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion,
+      List<String> noMemberRegions, String[] includeRegions, String[] excludeRegions,
+      InternalCache cache) {
+    // Include all regions
+    if (includeRegions == null) {
+      // Exclude these regions
+      List<String> excludedRegionList =
+          excludeRegions != null ? Arrays.asList(excludeRegions) : new ArrayList<>();
+
+      List<RebalanceOperationPerformer.MemberPRInfo> memberRegionList =
+          getMembersForEachRegion(excludedRegionList, cache);
+      membersForEachRegion.addAll(memberRegionList);
+    } else {
+      for (String regionName : includeRegions) {
+        DistributedMember memberForRegion = getOneMemberForRegion(regionName, cache);
+
+        // If we did not find a member for this region name, add it to the list of regions with no
+        // members
+        if (memberForRegion == null) {
+          noMemberRegions.add(regionName);
+        } else {
+          RebalanceOperationPerformer.MemberPRInfo memberPRInfo =
+              new RebalanceOperationPerformer.MemberPRInfo();
+          memberPRInfo.region = regionName;
+          memberPRInfo.dsMemberList.add(memberForRegion);
+          membersForEachRegion.add(memberPRInfo);
+        }
+      }
+    }
+  }
+
+  // Extracted for testing
+  List<RebalanceOperationPerformer.MemberPRInfo> getMembersForEachRegion(
+      List<String> excludedRegionList, InternalCache cache) {
+    return RebalanceOperationPerformer.getMemberRegionList(
+        ManagementService.getManagementService(cache), cache, excludedRegionList);
+  }
+
+  // Extracted for testing
+  DistributedMember getOneMemberForRegion(String regionName, InternalCache cache) {
+    String regionNameWithSeparator = regionName;
+    // The getAssociatedMembers method requires region names start with '/'
+    if (!regionName.startsWith("/")) {
+      regionNameWithSeparator = "/" + regionName;

Review comment:
       Good call




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r418222622



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/StatusRedundancyCommand.java
##########
@@ -0,0 +1,121 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class StatusRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "status redundancy";
+  private static final String COMMAND_HELP =
+      "Report the redundancy status for partitioned regions in connected members. The default is to report status for all regions.";
+  static final String INCLUDE_REGION = "include-region";
+  private static final String INCLUDE_REGION_HELP =
+      "Partitioned regions to be included when reporting redundancy status. Includes take precedence over excludes.";
+  static final String EXCLUDE_REGION = "exclude-region";
+  private static final String EXCLUDE_REGION_HELP =
+      "Partitioned regions to be excluded when reporting redundancy status.";
+
+  @CliCommand(value = COMMAND_NAME, help = COMMAND_HELP)
+  @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_DATA, CliStrings.TOPIC_GEODE_REGION})
+  @ResourceOperation(resource = ResourcePermission.Resource.DATA,
+      operation = ResourcePermission.Operation.READ)
+  public ResultModel execute(
+      @CliOption(key = INCLUDE_REGION, help = INCLUDE_REGION_HELP) String[] includeRegions,
+      @CliOption(key = EXCLUDE_REGION, help = EXCLUDE_REGION_HELP) String[] excludeRegions) {
+    RedundancyCommandUtils utils = getUtils();
+
+    List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion = new ArrayList<>();
+    List<String> includedRegionsWithNoMembers = new ArrayList<>();
+
+    utils.populateLists(membersForEachRegion, includedRegionsWithNoMembers, includeRegions,
+        excludeRegions, (InternalCache) getCache());
+
+    for (RebalanceOperationPerformer.MemberPRInfo prInfo : membersForEachRegion) {
+      // Filter out any members using older versions of Geode
+      List<DistributedMember> viableMembers =
+          utils.filterViableMembersForVersion(prInfo, REDUNDANCY_COMMAND_ADDED_VERSION);
+
+      if (viableMembers.size() == 0) {
+        // If no viable members were found, return with error status
+        return utils.getNoViableMembersResult(REDUNDANCY_COMMAND_ADDED_VERSION, prInfo.region);
+      } else {
+        // Update the MemberPRInfo with the viable members
+        prInfo.dsMemberList = viableMembers;
+      }
+    }
+
+    List<CliFunctionResult> functionResults =
+        executeFunctionOnMembers(includeRegions, excludeRegions, membersForEachRegion);
+
+    return utils.buildResultModelFromFunctionResults(functionResults, includedRegionsWithNoMembers,
+        true);
+  }
+
+  List<CliFunctionResult> executeFunctionOnMembers(String[] includeRegions, String[] excludeRegions,
+      List<RebalanceOperationPerformer.MemberPRInfo> membersForEachRegion) {
+
+    List<CliFunctionResult> functionResults = new ArrayList<>();
+    boolean shouldNotReassignPrimaries = false;

Review comment:
       Agreed. I've removed the redundant variables and introduced a comment to clarify the meaning of each of the function arguments instead.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r416133022



##########
File path: geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommandTest.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.management.cli.Result.Status.ERROR;
+import static org.apache.geode.management.cli.Result.Status.OK;
+import static org.apache.geode.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+
+public class RestoreRedundancyCommandTest {

Review comment:
       The results should be being examined in the DUnit tests for these commands, so the unit tests are just intended to confirm that the methods in the class are providing the expected behaviour.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r418172673



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/RestoreRedundancyCommand.java
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.management.internal.cli.commands.RedundancyCommandUtils.REDUNDANCY_COMMAND_ADDED_VERSION;
+import static org.apache.geode.management.internal.functions.CliFunctionResult.StatusState.ERROR;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.RedundancyCommandFunction;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.management.internal.operation.RebalanceOperationPerformer;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class RestoreRedundancyCommand extends GfshCommand {
+  static final String COMMAND_NAME = "restore redundancy";
+  private static final String COMMAND_HELP =
+      "Restore redundancy and optionally reassign primary bucket hosting for partitioned regions in connected members. The default is for all regions to have redundancy restored and for primary buckets to be reassigned for better load balance.";
+  static final String INCLUDE_REGION = "include-region";

Review comment:
       The existing `rebalance` command uses `--include-region` and `--exclude-region`, so that naming was kept here for consistency, and also in the `StatusRedundancyCommand` class.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [geode] DonalEvans commented on a change in pull request #5004: GEODE-7954: restore redundancy and status redundancy gfsh commands

Posted by GitBox <gi...@apache.org>.
DonalEvans commented on a change in pull request #5004:
URL: https://github.com/apache/geode/pull/5004#discussion_r416133323



##########
File path: geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/RedundancyCommandFunction.java
##########
@@ -0,0 +1,78 @@
+/*
+ * 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.apache.geode.cache.control.RestoreRedundancyResults.Status.ERROR;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.geode.cache.control.RestoreRedundancyOperation;
+import org.apache.geode.cache.control.RestoreRedundancyResults;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.management.cli.CliFunction;
+import org.apache.geode.management.internal.functions.CliFunctionResult;
+
+public class RedundancyCommandFunction extends CliFunction<Object[]> {
+  private static final long serialVersionUID = 5633636343813884996L;
+
+  @Override
+  public CliFunctionResult executeFunction(FunctionContext<Object[]> context) {
+    Object[] arguments = context.getArguments();
+    String[] includeRegions = (String[]) arguments[0];
+    Set<String> includeRegionsSet = null;
+    if (includeRegions != null) {
+      includeRegionsSet = new HashSet<>(Arrays.asList(includeRegions));
+    }
+
+    String[] excludeRegions = (String[]) arguments[1];
+    Set<String> excludeRegionsSet = null;
+    if (excludeRegions != null) {
+      excludeRegionsSet = new HashSet<>(Arrays.asList(excludeRegions));
+    }
+
+    boolean shouldReassignPrimaries = (boolean) arguments[2];
+
+    boolean isStatusCommand = false;
+    if (arguments.length > 3) {
+      isStatusCommand = (boolean) arguments[3];
+    }
+
+    RestoreRedundancyResults results;
+    try {
+      RestoreRedundancyOperation redundancyOperation =
+          context.getCache().getResourceManager().createRestoreRedundancyOperation();
+      redundancyOperation.includeRegions(includeRegionsSet);
+      redundancyOperation.excludeRegions(excludeRegionsSet);
+      if (isStatusCommand) {
+        results = redundancyOperation.redundancyStatus();
+      } else {
+        redundancyOperation.shouldReassignPrimaries(shouldReassignPrimaries);
+        results = redundancyOperation.start().get();
+      }
+    } catch (Exception ex) {

Review comment:
       Will do




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org