This is an automated email from the ASF dual-hosted git repository.
sarvekshayr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new eccb174c7b3 HDDS-15535. Container Balancer should validate
configuration and report startup failures to user (#10812)
eccb174c7b3 is described below
commit eccb174c7b353a82a3ff5bbc866eadd7307cfe55
Author: sreejasahithi <[email protected]>
AuthorDate: Wed Jul 29 12:38:19 2026 +0530
HDDS-15535. Container Balancer should validate configuration and report
startup failures to user (#10812)
---
.../balancer/ContainerBalancerConfiguration.java | 17 ++-
...inerLocationProtocolClientSideTranslatorPB.java | 4 +-
.../scm/container/balancer/ContainerBalancer.java | 158 +++++++++++++++++++++
.../container/balancer/ContainerBalancerTask.java | 16 +--
.../hdds/scm/server/SCMClientProtocolServer.java | 4 +-
.../container/balancer/TestContainerBalancer.java | 107 +++++++++++++-
.../scm/cli/ContainerBalancerStartSubcommand.java | 2 -
.../datanode/TestContainerBalancerSubCommand.java | 4 +-
.../hadoop/hdds/scm/TestFailoverWithSCMHA.java | 1 +
.../ozone/TestContainerBalancerOperations.java | 24 +++-
10 files changed, 304 insertions(+), 33 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerConfiguration.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerConfiguration.java
index 12ad1501009..65acb9da8e1 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerConfiguration.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerConfiguration.java
@@ -206,7 +206,7 @@ public void setIterations(int count) {
* Gets the maximum percentage of healthy, in-service datanodes that will be
* involved in balancing in one iteration.
*
- * @return percentage as an integer from 0 up to and including 100
+ * @return percentage as an integer greater than 0 up to and including 100
*/
public int getMaxDatanodesPercentageToInvolvePerIteration() {
return maxDatanodesPercentageToInvolvePerIteration;
@@ -253,6 +253,17 @@ public double getMaxDatanodesRatioToInvolvePerIteration() {
return maxDatanodesPercentageToInvolvePerIteration / 100d;
}
+ /**
+ * Computes the maximum number of datanodes that may be involved in an
+ * iteration for the given eligible datanode count.
+ *
+ * @param eligibleDatanodeCount number of healthy, in-service datanodes.
+ * @return maximum datanodes that may be involved in one iteration
+ */
+ public int computeMaxDatanodesToInvolvePerIteration(int
eligibleDatanodeCount) {
+ return (int) (getMaxDatanodesRatioToInvolvePerIteration() *
eligibleDatanodeCount);
+ }
+
/**
* Sets the maximum percentage of healthy, in-service datanodes that will be
* involved in balancing in one iteration.
@@ -266,10 +277,10 @@ public double getMaxDatanodesRatioToInvolvePerIteration()
{
*/
public void setMaxDatanodesPercentageToInvolvePerIteration(
int maxDatanodesPercentageToInvolvePerIteration) {
- if (maxDatanodesPercentageToInvolvePerIteration < 0 ||
+ if (maxDatanodesPercentageToInvolvePerIteration <= 0 ||
maxDatanodesPercentageToInvolvePerIteration > 100) {
throw new IllegalArgumentException(String.format("Argument %d is " +
- "illegal. Percentage must be from 0 up to and including 100.",
+ "illegal. Percentage must be greater than 0 up to and including
100.",
maxDatanodesPercentageToInvolvePerIteration));
}
this.maxDatanodesPercentageToInvolvePerIteration =
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
index 39c47ae3ae0..7808cb286a2 100644
---
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
@@ -1005,9 +1005,9 @@ public StartContainerBalancerResponseProto
startContainerBalancer(
}
if (maxDatanodesPercentageToInvolvePerIteration.isPresent()) {
int mdti = maxDatanodesPercentageToInvolvePerIteration.get();
- Preconditions.checkState(mdti >= 0,
+ Preconditions.checkState(mdti > 0,
"Max Datanodes Percentage To Involve Per Iteration must be " +
- "greater than equal to zero.");
+ "greater than zero.");
Preconditions.checkState(mdti <= 100,
"Max Datanodes Percentage To Involve Per Iteration must be " +
"lesser than equal to hundred.");
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancer.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancer.java
index 3631df49f5a..159551fe4ca 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancer.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancer.java
@@ -17,10 +17,15 @@
package org.apache.hadoop.hdds.scm.container.balancer;
+import static
org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeOperationalState.IN_SERVICE;
+import static
org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.HEALTHY;
+
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.time.Duration;
import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -28,11 +33,16 @@
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.StorageUnit;
import org.apache.hadoop.hdds.fs.DUFactory;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import
org.apache.hadoop.hdds.protocol.proto.HddsProtos.ContainerBalancerConfigurationProto;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
+import org.apache.hadoop.hdds.scm.container.ContainerManager;
+import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
import org.apache.hadoop.hdds.scm.ha.SCMContext;
import org.apache.hadoop.hdds.scm.ha.StatefulService;
import org.apache.hadoop.hdds.scm.ha.StatefulServiceDefinition;
+import org.apache.hadoop.hdds.scm.node.DatanodeInfo;
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -470,6 +480,27 @@ private void
validateConfiguration(ContainerBalancerConfiguration conf)
" than ozone.scm.container.size");
}
+ if (conf.getMaxSizeEnteringTarget() > conf.getMaxSizeToMovePerIteration())
{
+ LOG.warn("hdds.container.balancer.size.entering.target.max {} should be "
+ + "less than or equal to hdds.container.balancer.size.moved.max"
+ + ".per.iteration {}",
+ conf.getMaxSizeEnteringTarget(),
conf.getMaxSizeToMovePerIteration());
+ throw new InvalidContainerBalancerConfigurationException(
+ "hdds.container.balancer.size.entering.target.max should be less "
+ + "than or equal to hdds.container.balancer.size.moved.max.per"
+ + ".iteration");
+ }
+ if (conf.getMaxSizeLeavingSource() > conf.getMaxSizeToMovePerIteration()) {
+ LOG.warn("hdds.container.balancer.size.leaving.source.max {} should be "
+ + "less than or equal to hdds.container.balancer.size.moved.max"
+ + ".per.iteration {}",
+ conf.getMaxSizeLeavingSource(), conf.getMaxSizeToMovePerIteration());
+ throw new InvalidContainerBalancerConfigurationException(
+ "hdds.container.balancer.size.leaving.source.max should be less "
+ + "than or equal to hdds.container.balancer.size.moved.max.per"
+ + ".iteration");
+ }
+
// balancing interval should be greater than DUFactory refresh period
DUFactory.Conf duConf = ozoneConfiguration.getObject(DUFactory.Conf.class);
long refreshPeriod = duConf.getRefreshPeriod().toMillis();
@@ -510,6 +541,133 @@ private void
validateConfiguration(ContainerBalancerConfiguration conf)
validateNodeList(conf.getIncludeNodes(), "included");
validateNodeList(conf.getExcludeNodes(), "excluded");
+ validateIncludeExcludeLists(conf);
+ validateIncludeContainersExist(conf);
+ validateEligibleDatanodePool(conf);
+ }
+
+ /**
+ * Rejects include lists that are fully covered by the corresponding exclude
+ * lists, which would leave no datanodes or containers to balance.
+ */
+ private void validateIncludeExcludeLists(ContainerBalancerConfiguration conf)
+ throws InvalidContainerBalancerConfigurationException {
+ Set<String> includeNodes = conf.getIncludeNodes();
+ Set<String> excludeNodes = conf.getExcludeNodes();
+ if (!includeNodes.isEmpty() && !excludeNodes.isEmpty()) {
+ boolean allIncludedNodesExcluded = true;
+ for (String includedNode : includeNodes) {
+ if (!isIncludedNodeExcluded(includedNode, excludeNodes)) {
+ allIncludedNodesExcluded = false;
+ break;
+ }
+ }
+ if (allIncludedNodesExcluded) {
+ throw new InvalidContainerBalancerConfigurationException(
+ "include-datanodes is a subset of exclude-datanodes, no datanode
can participate in balancing.");
+ }
+ }
+
+ Set<ContainerID> includeContainers = conf.getIncludeContainers();
+ Set<ContainerID> excludeContainers = conf.getExcludeContainers();
+ if (!includeContainers.isEmpty() &&
excludeContainers.containsAll(includeContainers)) {
+ throw new InvalidContainerBalancerConfigurationException(
+ "include-containers is a subset of exclude-containers, no container
can be selected for balancing.");
+ }
+ }
+
+ private boolean isIncludedNodeExcluded(String includedNode, Set<String>
excludeNodes) {
+ if (excludeNodes.contains(includedNode)) {
+ return true;
+ }
+ for (DatanodeDetails dn :
scm.getScmNodeManager().getNodesByAddress(includedNode)) {
+ if (excludeNodes.contains(dn.getHostName()) ||
excludeNodes.contains(dn.getIpAddress())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Rejects non-empty include-containers lists when any listed container ID
+ * does not exist in SCM.
+ */
+ private void validateIncludeContainersExist(ContainerBalancerConfiguration
conf)
+ throws InvalidContainerBalancerConfigurationException {
+ Set<ContainerID> includeContainers = conf.getIncludeContainers();
+ if (includeContainers.isEmpty()) {
+ return;
+ }
+
+ ContainerManager containerManager = scm.getContainerManager();
+ List<ContainerID> missingContainers = new ArrayList<>();
+ for (ContainerID containerID : includeContainers) {
+ try {
+ containerManager.getContainer(containerID);
+ } catch (ContainerNotFoundException e) {
+ missingContainers.add(containerID);
+ }
+ }
+
+ if (!missingContainers.isEmpty()) {
+ throw new InvalidContainerBalancerConfigurationException(
+ "Container Balancer cannot start: included container ID(s) " +
missingContainers
+ + " do not exist in SCM.");
+ }
+ }
+
+ /**
+ * Validates that enough healthy, in-service datanodes are eligible and that
+ * {@link
ContainerBalancerConfiguration#getMaxDatanodesRatioToInvolvePerIteration()}
+ * allows at least one source and one target datanode per iteration.
+ */
+ private void validateEligibleDatanodePool(ContainerBalancerConfiguration
conf)
+ throws InvalidContainerBalancerConfigurationException {
+ int eligibleCount = countEligibleDatanodes(conf);
+ if (eligibleCount < 2) {
+ throw new InvalidContainerBalancerConfigurationException(String.format(
+ "Container Balancer found %d eligible datanode(s) but requires at
least 2.",
+ eligibleCount));
+ }
+ int maxDatanodesToInvolve =
conf.computeMaxDatanodesToInvolvePerIteration(eligibleCount);
+ if (maxDatanodesToInvolve < 2) {
+ throw new InvalidContainerBalancerConfigurationException(String.format(
+ "max-datanodes-percentage-to-involve-per-iteration=%d allows at most
"
+ + "%d datanode(s) per iteration with %d eligible datanode(s), "
+ + "but at least 2 are required for a source and target datanode "
+ + "pair.",
+ conf.getMaxDatanodesPercentageToInvolvePerIteration(),
+ maxDatanodesToInvolve, eligibleCount));
+ }
+ }
+
+ /**
+ * Counts healthy, in-service datanodes that can participate in balancing
after
+ * applying include/exclude datanode configuration.
+ */
+ private int countEligibleDatanodes(ContainerBalancerConfiguration conf) {
+ Set<String> excludeNodes = conf.getExcludeNodes();
+ Set<String> includeNodes = conf.getIncludeNodes();
+ List<DatanodeInfo> healthyNodes =
scm.getScmNodeManager().getNodes(IN_SERVICE, HEALTHY);
+ int eligibleCount = 0;
+ for (DatanodeDetails datanode : healthyNodes) {
+ if (!shouldExcludeDatanode(datanode, excludeNodes, includeNodes)) {
+ eligibleCount++;
+ }
+ }
+ return eligibleCount;
+ }
+
+ static boolean shouldExcludeDatanode(DatanodeDetails datanode,
+ Set<String> excludeNodes, Set<String> includeNodes) {
+ if (excludeNodes.contains(datanode.getHostName()) ||
+ excludeNodes.contains(datanode.getIpAddress())) {
+ return true;
+ } else if (!includeNodes.isEmpty()) {
+ return !includeNodes.contains(datanode.getHostName()) &&
+ !includeNodes.contains(datanode.getIpAddress());
+ }
+ return false;
}
public ContainerBalancerMetrics getMetrics() {
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java
index 38554253bab..bb2b4c87325 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java
@@ -85,7 +85,6 @@ public class ContainerBalancerTask implements Runnable {
private ContainerBalancer containerBalancer;
private final SCMContext scmContext;
private int totalNodesInCluster;
- private double maxDatanodesRatioToInvolvePerIteration;
private long maxSizeToMovePerIteration;
private int countDatanodesInvolvedPerIteration;
private long sizeScheduledForMoveInLatestIteration;
@@ -537,8 +536,6 @@ private boolean initializeIteration() {
return false;
}
- this.maxDatanodesRatioToInvolvePerIteration =
- config.getMaxDatanodesRatioToInvolvePerIteration();
this.maxSizeToMovePerIteration = config.getMaxSizeToMovePerIteration();
this.excludeNodes = config.getExcludeNodes();
@@ -939,7 +936,7 @@ private boolean reachedMaxSizeToMovePerIteration() {
private boolean adaptWhenNearingIterationLimits() {
// check if we're nearing max datanodes to involve
int maxDatanodesToInvolve =
- (int) (maxDatanodesRatioToInvolvePerIteration * totalNodesInCluster);
+ config.computeMaxDatanodesToInvolvePerIteration(totalNodesInCluster);
if (countDatanodesInvolvedPerIteration + 1 == maxDatanodesToInvolve) {
/* We're one datanode away from reaching the limit. Restrict potential
targets to targets that have already been selected.
@@ -966,7 +963,7 @@ private boolean adaptWhenNearingIterationLimits() {
private boolean adaptOnReachingIterationLimits() {
// check if we've reached max datanodes to involve limit
int maxDatanodesToInvolve =
- (int) (maxDatanodesRatioToInvolvePerIteration * totalNodesInCluster);
+ config.computeMaxDatanodesToInvolvePerIteration(totalNodesInCluster);
if (countDatanodesInvolvedPerIteration == maxDatanodesToInvolve) {
// restrict both to already selected sources and targets
findTargetStrategy.resetPotentialTargets(selectedTargets);
@@ -1172,14 +1169,7 @@ private List<DatanodeUsageInfo> getPotentialSources() {
* @return true if Datanode should be excluded, else false
*/
private boolean shouldExcludeDatanode(DatanodeDetails datanode) {
- if (excludeNodes.contains(datanode.getHostName()) ||
- excludeNodes.contains(datanode.getIpAddress())) {
- return true;
- } else if (!includeNodes.isEmpty()) {
- return !includeNodes.contains(datanode.getHostName()) &&
- !includeNodes.contains(datanode.getIpAddress());
- }
- return false;
+ return ContainerBalancer.shouldExcludeDatanode(datanode, excludeNodes,
includeNodes);
}
/**
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
index 7324ae32b6d..acb3e64a3ca 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
@@ -1235,9 +1235,9 @@ public StartContainerBalancerResponseProto
startContainerBalancer(
int mdti = maxDatanodesPercentageToInvolvePerIteration.get();
auditMap.put("maxDatanodesPercentageToInvolvePerIteration",
String.valueOf(mdti));
- if (mdti < 0 || mdti > 100) {
+ if (mdti <= 0 || mdti > 100) {
throw new IOException("Max Datanodes Percentage To Involve Per
Iteration" +
- "should be specified in the range [0, 100]");
+ "should be specified in the range (0, 100]");
}
cbc.setMaxDatanodesPercentageToInvolvePerIteration(mdti);
}
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancer.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancer.java
index 4864f235d5b..8931c9d6719 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancer.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancer.java
@@ -19,6 +19,8 @@
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_NODE_REPORT_INTERVAL;
import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SCM_WAIT_TIME_AFTER_SAFE_MODE_EXIT;
+import static
org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeOperationalState.IN_SERVICE;
+import static
org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.HEALTHY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -36,20 +38,27 @@
import com.google.protobuf.ByteString;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
+import org.apache.hadoop.hdds.scm.container.ContainerManager;
+import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
import org.apache.hadoop.hdds.scm.ha.SCMContext;
import org.apache.hadoop.hdds.scm.ha.SCMServiceManager;
import org.apache.hadoop.hdds.scm.ha.StatefulServiceStateManager;
import org.apache.hadoop.hdds.scm.ha.StatefulServiceStateManagerImpl;
+import org.apache.hadoop.hdds.scm.node.DatanodeInfo;
import org.apache.hadoop.hdds.scm.node.NodeManager;
import org.apache.hadoop.hdds.scm.node.states.NodeNotFoundException;
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.ozone.test.GenericTestUtils;
import org.apache.ozone.test.GenericTestUtils.LogCapturer;
import org.junit.jupiter.api.BeforeEach;
@@ -67,6 +76,7 @@ public class TestContainerBalancer {
private ContainerBalancer containerBalancer;
private StorageContainerManager scm;
+ private NodeManager nodeManager;
private ContainerBalancerConfiguration balancerConfiguration;
private Map<String, ByteString> serviceToConfigMap = new HashMap<>();
private OzoneConfiguration conf;
@@ -95,6 +105,9 @@ public void setup() throws IOException,
NodeNotFoundException,
GenericTestUtils.setLogLevel(ContainerBalancer.class, Level.DEBUG);
when(scm.getScmNodeManager()).thenReturn(mock(NodeManager.class));
+ nodeManager = scm.getScmNodeManager();
+ List<DatanodeInfo> eligibleDatanodes = createEligibleDatanodes(10);
+ when(nodeManager.getNodes(IN_SERVICE,
HEALTHY)).thenReturn(eligibleDatanodes);
when(scm.getScmContext()).thenReturn(SCMContext.emptyContext());
when(scm.getConfiguration()).thenReturn(conf);
when(scm.getStatefulServiceStateManager()).thenReturn(serviceStateManager);
@@ -296,12 +309,11 @@ public void testGetBalancerStatusInfo() throws Exception {
@Test
public void testStartBalancerWithInvalidNodes() throws Exception {
- NodeManager nm = scm.getScmNodeManager();
String validHost = "1.2.3.4";
String invalidHost = "invalid-host-name";
-
when(nm.getNodesByAddress(invalidHost)).thenReturn(Collections.emptyList());
-
when(nm.getNodesByAddress(validHost)).thenReturn(Collections.singletonList(mock(DatanodeDetails.class)));
+
when(nodeManager.getNodesByAddress(invalidHost)).thenReturn(Collections.emptyList());
+
when(nodeManager.getNodesByAddress(validHost)).thenReturn(Collections.singletonList(mock(DatanodeDetails.class)));
// Test invalid includeNodes
balancerConfiguration.setIncludeNodes(invalidHost);
@@ -321,7 +333,7 @@ public void testStartBalancerWithInvalidNodes() throws
Exception {
// Test a valid case
balancerConfiguration.setExcludeNodes("");
- balancerConfiguration.setIncludeNodes(validHost);
+ balancerConfiguration.setIncludeNodes("");
assertDoesNotThrow(() -> startBalancer(balancerConfiguration));
assertSame(ContainerBalancerTask.Status.RUNNING,
containerBalancer.getBalancerStatus());
@@ -371,6 +383,93 @@ public void testGetBalancerStatusInfoAfterScmStop() throws
Exception {
assertNotNull(statusInfo.getStoppedAt());
}
+ /**
+ * Tests new startup validation for conflicting include/exclude lists.
+ */
+ @Test
+ public void testRejectConflictingIncludeExcludeLists() throws Exception {
+ when(nodeManager.getNodesByAddress("dn0"))
+ .thenReturn(Collections.singletonList(mock(DatanodeDetails.class)));
+ when(nodeManager.getNodesByAddress("dn1"))
+ .thenReturn(Collections.singletonList(mock(DatanodeDetails.class)));
+ balancerConfiguration.setIncludeNodes("dn0,dn1");
+ balancerConfiguration.setExcludeNodes("dn0,dn1");
+
+ InvalidContainerBalancerConfigurationException ex =
+ assertThrows(InvalidContainerBalancerConfigurationException.class,
+ () -> containerBalancer.startBalancer(balancerConfiguration));
+ assertThat(ex.getMessage()).contains(
+ "include-datanodes is a subset of exclude-datanodes");
+ assertSame(ContainerBalancerTask.Status.STOPPED,
containerBalancer.getBalancerStatus());
+
+ balancerConfiguration.setIncludeNodes("");
+ balancerConfiguration.setExcludeNodes("");
+ balancerConfiguration.setIncludeContainers("1, 2");
+ balancerConfiguration.setExcludeContainers("1,2,3");
+ ex = assertThrows(InvalidContainerBalancerConfigurationException.class,
+ () -> containerBalancer.startBalancer(balancerConfiguration));
+ assertThat(ex.getMessage()).contains(
+ "include-containers is a subset of exclude-containers");
+ assertSame(ContainerBalancerTask.Status.STOPPED,
containerBalancer.getBalancerStatus());
+ }
+
+ /**
+ * Tests new startup validation for include-containers, datanode pool, and
+ * size limits.
+ */
+ @Test
+ public void testRejectInvalidStartupConfiguration() throws Exception {
+ ContainerManager containerManager = mock(ContainerManager.class);
+ when(scm.getContainerManager()).thenReturn(containerManager);
+ when(containerManager.getContainer(any(ContainerID.class)))
+ .thenThrow(new ContainerNotFoundException(ContainerID.valueOf(1)));
+
+ balancerConfiguration.setIncludeContainers("1");
+ InvalidContainerBalancerConfigurationException ex =
+ assertThrows(InvalidContainerBalancerConfigurationException.class,
+ () -> containerBalancer.startBalancer(balancerConfiguration));
+ assertThat(ex.getMessage()).contains("do not exist in SCM");
+ assertSame(ContainerBalancerTask.Status.STOPPED,
containerBalancer.getBalancerStatus());
+
+ balancerConfiguration.setIncludeContainers("");
+ List<DatanodeInfo> fiveDatanodes = createEligibleDatanodes(5);
+ when(nodeManager.getNodes(IN_SERVICE, HEALTHY)).thenReturn(fiveDatanodes);
+ balancerConfiguration.setMaxDatanodesPercentageToInvolvePerIteration(20);
+ ex = assertThrows(InvalidContainerBalancerConfigurationException.class,
+ () -> containerBalancer.startBalancer(balancerConfiguration));
+ assertThat(ex.getMessage()).contains("at least 2 are required for a source
and target datanode pair.");
+ assertSame(ContainerBalancerTask.Status.STOPPED,
containerBalancer.getBalancerStatus());
+
+ balancerConfiguration.setMaxSizeToMovePerIteration(100 * OzoneConsts.GB);
+ balancerConfiguration.setMaxSizeEnteringTarget(200 * OzoneConsts.GB);
+ ex = assertThrows(InvalidContainerBalancerConfigurationException.class,
+ () -> containerBalancer.startBalancer(balancerConfiguration));
+ assertThat(ex.getMessage()).contains(
+ "hdds.container.balancer.size.entering.target.max should be less than
or "
+ + "equal to hdds.container.balancer.size.moved.max.per.iteration");
+ assertSame(ContainerBalancerTask.Status.STOPPED,
containerBalancer.getBalancerStatus());
+
+ balancerConfiguration.setMaxSizeEnteringTarget(100 * OzoneConsts.GB);
+ balancerConfiguration.setMaxSizeLeavingSource(200 * OzoneConsts.GB);
+ ex = assertThrows(InvalidContainerBalancerConfigurationException.class,
+ () -> containerBalancer.startBalancer(balancerConfiguration));
+ assertThat(ex.getMessage()).contains(
+ "hdds.container.balancer.size.leaving.source.max should be less than
or "
+ + "equal to hdds.container.balancer.size.moved.max.per.iteration");
+ assertSame(ContainerBalancerTask.Status.STOPPED,
containerBalancer.getBalancerStatus());
+ }
+
+ private static List<DatanodeInfo> createEligibleDatanodes(int count) {
+ List<DatanodeInfo> datanodes = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ DatanodeInfo datanode = mock(DatanodeInfo.class);
+ when(datanode.getHostName()).thenReturn("dn" + i);
+ when(datanode.getIpAddress()).thenReturn("10.0.0." + i);
+ datanodes.add(datanode);
+ }
+ return datanodes;
+ }
+
private void startBalancer(ContainerBalancerConfiguration config)
throws IllegalContainerBalancerStateException, IOException,
InvalidContainerBalancerConfigurationException, TimeoutException {
diff --git
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerStartSubcommand.java
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerStartSubcommand.java
index 2cab942f618..09de9e2b758 100644
---
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerStartSubcommand.java
+++
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerStartSubcommand.java
@@ -143,10 +143,8 @@ public void execute(ScmClient scmClient) throws
IOException {
System.out.println("Container Balancer started successfully.");
} else {
String reason = "";
- System.err.println("Failed to start Container Balancer.");
if (response.hasMessage()) {
reason = response.getMessage();
- System.err.printf("Failure reason: %s%n", reason);
}
throw new IOException("Failed to start Container Balancer. " + reason);
}
diff --git
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestContainerBalancerSubCommand.java
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestContainerBalancerSubCommand.java
index 613ca37e586..407785d48a3 100644
---
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestContainerBalancerSubCommand.java
+++
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestContainerBalancerSubCommand.java
@@ -596,8 +596,8 @@ public void
testContainerBalancerStartSubcommandWhenBalancerIsRunning()
.setStart(false)
.setMessage("")
.build());
- assertThrows(IOException.class, () -> startCmd.execute(scmClient));
- assertThat(err.get()).containsPattern(FAILED_TO_START);
+ IOException ex = assertThrows(IOException.class, () ->
startCmd.execute(scmClient));
+ assertThat(ex.getMessage()).containsPattern(FAILED_TO_START);
}
@Test
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestFailoverWithSCMHA.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestFailoverWithSCMHA.java
index bbbee76cede..c2c7b3c6e23 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestFailoverWithSCMHA.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestFailoverWithSCMHA.java
@@ -171,6 +171,7 @@ public void
testContainerBalancerPersistsConfigurationInAllSCMs()
ScmClient scmClient = new ContainerOperationClient(conf);
// assert that container balancer is not running right now
assertFalse(scmClient.getContainerBalancerStatus());
+
conf.setInt("hdds.container.balancer.datanodes.involved.max.percentage.per.iteration",
100);
ContainerBalancerConfiguration balancerConf =
conf.getObject(ContainerBalancerConfiguration.class);
ContainerBalancer containerBalancer = leader.getContainerBalancer();
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestContainerBalancerOperations.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestContainerBalancerOperations.java
index bb8d0704538..80d2c11079f 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestContainerBalancerOperations.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestContainerBalancerOperations.java
@@ -25,17 +25,21 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.scm.PlacementPolicy;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient;
import org.apache.hadoop.hdds.scm.client.ScmClient;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import
org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerConfiguration;
+import
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
import
org.apache.hadoop.hdds.scm.container.placement.algorithms.SCMContainerPlacementCapacity;
import org.apache.ozone.test.GenericTestUtils;
import org.junit.jupiter.api.AfterAll;
@@ -84,7 +88,7 @@ public void testContainerBalancerCLIOperations() throws
Exception {
Optional<Integer> iterations = Optional.of(10000);
Optional<Integer> maxDatanodesPercentageToInvolvePerIteration =
Optional.of(100);
- Optional<Long> maxSizeToMovePerIterationInGB = Optional.of(1L);
+ Optional<Long> maxSizeToMovePerIterationInGB = Optional.of(6L);
Optional<Long> maxSizeEnteringTargetInGB = Optional.of(6L);
Optional<Long> maxSizeLeavingSourceInGB = Optional.of(6L);
Optional<Integer> balancingInterval = Optional.of(70);
@@ -149,14 +153,24 @@ public void testIfCBCLIOverridesConfigs() throws
Exception {
//CLI option for iterations and balancing interval is not passed
Optional<Integer> iterations = Optional.empty();
Optional<Integer> balancingInterval = Optional.empty();
- String excludedContainersList = "1,2,3";
- String includedContainersList = "4,5";
+ List<ContainerWithPipeline> createdContainers = new ArrayList<>(5);
+ for (int i = 0; i < 5; i++) {
+ createdContainers.add(containerBalancerClient.createContainer(
+ HddsProtos.ReplicationType.RATIS,
+ HddsProtos.ReplicationFactor.ONE,
+ OzoneConsts.OZONE));
+ }
+ String excludedContainersList =
createdContainers.get(0).getContainerInfo().getContainerID() + ","
+ + createdContainers.get(1).getContainerInfo().getContainerID() + ","
+ + createdContainers.get(2).getContainerInfo().getContainerID();
+ String includedContainersList =
createdContainers.get(3).getContainerInfo().getContainerID() + ","
+ + createdContainers.get(4).getContainerInfo().getContainerID();
//CLI options are passed
Optional<Double> threshold = Optional.of(0.1);
Optional<Integer> maxDatanodesPercentageToInvolvePerIteration =
Optional.of(100);
- Optional<Long> maxSizeToMovePerIterationInGB = Optional.of(1L);
+ Optional<Long> maxSizeToMovePerIterationInGB = Optional.of(6L);
Optional<Long> maxSizeEnteringTargetInGB = Optional.of(6L);
Optional<Long> maxSizeLeavingSourceInGB = Optional.of(6L);
Optional<Integer> moveTimeout = Optional.of(65);
@@ -212,7 +226,7 @@ public void testStopBalancerIdempotent() throws IOException
{
Optional<Integer> iterations = Optional.of(10000);
Optional<Integer> maxDatanodesPercentageToInvolvePerIteration =
Optional.of(100);
- Optional<Long> maxSizeToMovePerIterationInGB = Optional.of(1L);
+ Optional<Long> maxSizeToMovePerIterationInGB = Optional.of(6L);
Optional<Long> maxSizeEnteringTargetInGB = Optional.of(6L);
Optional<Long> maxSizeLeavingSourceInGB = Optional.of(6L);
Optional<Integer> balancingInterval = Optional.of(70);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]