You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@ozone.apache.org by "sreejasahithi (via GitHub)" <gi...@apache.org> on 2026/09/18 04:46:59 UTC
[PR] HDDS 16179. Dry run command for container balancer with profiles [ozone]
sreejasahithi opened a new pull request, #11255:
URL: https://github.com/apache/ozone/pull/11255
## What changes were proposed in this pull request?
This PR adds a dry-run subcommand to ozone admin containerbalancer so users can estimate how much data the Container Balancer would move, how many iterations it would need, and how long it could take(upper bound), without starting the balancer.
Flow:
- Fetch datanode usage from SCM(via the existing getDatanodeUsageInfo RPC)
- Reads balancer limits, timeouts, and profile presets from local Ozone configuration (with optional CLI overrides)
- Analyze the cluster and estimate one or more throttling profiles
For each profile is reports:
- bytes to move
- per-iteration throughput
- estimated iterations
- upper-bound duration (with a +30% planning buffer)
Key changes:
- Adds ozone admin containerbalancer dry-run with optional --profile slow|medium|fast. If --profile is omitted, all three profiles are estimated.
- Introduces SLOW, MEDIUM, and FAST throttling profiles with new hdds.container.balancer.profile.* config keys for datanode involvement and per-node move limits.
- Adds ContainerBalancerAdvisor and ContainerBalancerEstimation for dry-run cluster analysis and estimation.
- Extracts shared CLI options into ContainerBalancerConfigOptions, reused by start and dry-run (start behaviour unchanged).
- When all three profiles are estimated, a validation failure in one profile (e.g. FAST) does not block SLOW/MEDIUM results.
Note: start does not support --profile yet.
## What is the link to the Apache JIRA
[HDDS-16179](https://issues.apache.org/jira/browse/HDDS-16179)
## How was this patch tested?
Unit tests: TestContainerBalancerAdvisor, TestContainerBalancerSubCommand
Manual tested the dry-run command in a cluster:
1. ozone admin containerbalancer dry-run
```
Profile: SLOW
Based on:
Datanode involvement: 40%
Max entering target: 10 GB / node
Max leaving source: 10 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 73.62 GB
Per iteration (estimate): ~10 GB
Estimated iterations: 8 (planning estimate: 11, includes +30% buffer)
Estimated duration: upper bound ~18.0 hours (planning estimate: ~1.0 days, includes +30% buffer)
(assumes full move timeout + interval each cycle)
Profile: MEDIUM
Based on:
Datanode involvement: 40%
Max entering target: 26 GB / node
Max leaving source: 26 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 73.62 GB
Per iteration (estimate): ~26 GB
Estimated iterations: 3 (planning estimate: 4, includes +30% buffer)
Estimated duration: upper bound ~6.8 hours (planning estimate: ~9.0 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
Profile: FAST
Based on:
Datanode involvement: 40%
Max entering target: 100 GB / node
Max leaving source: 100 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 73.62 GB
Per iteration (estimate): ~73.62 GB
Estimated iterations: 1 (planning estimate: 2, includes +30% buffer)
Estimated duration: upper bound ~2.3 hours (planning estimate: ~4.5 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
```
2. ozone admin containerbalancer dry-run --threshold 5
```
Profile: SLOW
Based on:
Datanode involvement: 40%
Max entering target: 10 GB / node
Max leaving source: 10 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 111.59 GB
Per iteration (estimate): ~10 GB
Estimated iterations: 12 (planning estimate: 16, includes +30% buffer)
Estimated duration: upper bound ~1.1 days (planning estimate: ~1.5 days, includes +30% buffer)
(assumes full move timeout + interval each cycle)
Profile: MEDIUM
Based on:
Datanode involvement: 40%
Max entering target: 26 GB / node
Max leaving source: 26 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 111.59 GB
Per iteration (estimate): ~26 GB
Estimated iterations: 5 (planning estimate: 7, includes +30% buffer)
Estimated duration: upper bound ~11.3 hours (planning estimate: ~15.8 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
Profile: FAST
Based on:
Datanode involvement: 40%
Max entering target: 100 GB / node
Max leaving source: 100 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 111.59 GB
Per iteration (estimate): ~100 GB
Estimated iterations: 2 (planning estimate: 3, includes +30% buffer)
Estimated duration: upper bound ~4.5 hours (planning estimate: ~6.8 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
```
3. ozone admin containerbalancer dry-run --profile MEDIUM
```
Profile: MEDIUM
Based on:
Datanode involvement: 40%
Max entering target: 26 GB / node
Max leaving source: 26 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 73.62 GB
Per iteration (estimate): ~26 GB
Estimated iterations: 3 (planning estimate: 4, includes +30% buffer)
Estimated duration: upper bound ~6.8 hours (planning estimate: ~9.0 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
```
4. ozone admin containerbalancer dry-run --threshold 5 --max-size-entering-target-in-gb 15 --max-size-leaving-source-in-gb 15 --balancing-iteration-interval-minutes 30 --profile SLOW
```
Profile: SLOW
Based on:
Datanode involvement: 40%
Max entering target: 15 GB / node
Max leaving source: 15 GB / node
Max per iteration: 500 GB
Move timeout: 65 min
Balancing interval: 30 min
Bytes to move: 111.59 GB
Per iteration (estimate): ~15 GB
Estimated iterations: 8 (planning estimate: 11, includes +30% buffer)
Estimated duration: upper bound ~12.7 hours (planning estimate: ~17.4 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
```
5. ozone admin containerbalancer dry-run --max-size-to-move-per-iteration-in-gb 70 --threshold 5
```
Profile: SLOW
Based on:
Datanode involvement: 40%
Max entering target: 10 GB / node
Max leaving source: 10 GB / node
Max per iteration: 70 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 111.46 GB
Per iteration (estimate): ~10 GB
Estimated iterations: 12 (planning estimate: 16, includes +30% buffer)
Estimated duration: upper bound ~1.1 days (planning estimate: ~1.5 days, includes +30% buffer)
(assumes full move timeout + interval each cycle)
Profile: MEDIUM
Based on:
Datanode involvement: 40%
Max entering target: 26 GB / node
Max leaving source: 26 GB / node
Max per iteration: 70 GB
Move timeout: 65 min
Balancing interval: 70 min
Bytes to move: 111.46 GB
Per iteration (estimate): ~26 GB
Estimated iterations: 5 (planning estimate: 7, includes +30% buffer)
Estimated duration: upper bound ~11.3 hours (planning estimate: ~15.8 hours, includes +30% buffer)
(assumes full move timeout + interval each cycle)
Profile: FAST
Based on:
Datanode involvement: 40%
Max entering target: 100 GB / node
Max leaving source: 100 GB / node
Max per iteration: 70 GB
Move timeout: 65 min
Balancing interval: 70 min
Estimation failed: max-size-entering-target must be less than or equal to max-size-to-move-per-iteration.
```
Green CI: https://github.com/sreejasahithi/ozone/actions/runs/35199786999
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Dry run command for container balancer with profiles [ozone]
Posted by "sravani-revuri (via GitHub)" <gi...@apache.org>.
sravani-revuri commented on code in PR #11255:
URL: https://github.com/apache/ozone/pull/11255#discussion_r4070843271
##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerConfigOptions.java:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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.hadoop.hdds.scm.cli;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerAdvisor;
+import org.apache.hadoop.ozone.OzoneConsts;
+import picocli.CommandLine.Option;
+
+/**
+ * Shared Picocli options for container balancer commands.
+ */
+public class ContainerBalancerConfigOptions {
+
+ @Option(names = {"-t", "--threshold"},
+ description = "Percentage deviation from average utilization of " +
+ "the cluster after which a datanode will be rebalanced. The value " +
+ "should be in the range [0.0, 100.0), with a default of 10 " +
+ "(specify '10' for 10%%).")
+ private Optional<Double> threshold;
+
+ @Option(names = {"-d", "--max-datanodes-percentage-to-involve-per-iteration"},
+ description = "Max percentage of healthy, in service datanodes " +
+ "that can be involved in balancing in one iteration. The value " +
+ "should be in the range [0,100]. When omitted on dry-run, each profile uses its default preset. " +
Review Comment:
here the range says [0,100] which means it is inclusive of 0 but the validateMaxDatanodesPercentageToInvolvePerIteration thows error when it is 0. can this be changed to (0,100] ?
##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerDryRunSubcommand.java:
##########
@@ -0,0 +1,166 @@
+/*
+ * 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.hadoop.hdds.scm.cli;
+
+import static org.apache.hadoop.util.StringUtils.byteDesc;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerAdvisor;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerEstimation;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerProfile;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Estimates container balancer bytes to move, per iteration bytes, estimated iterations, estimated duration
+ * without starting the balancer.
+ */
+@Command(
+ name = "dry-run",
+ description = "Estimate container balancer bytes to move, iterations, per iteration bytes " +
+ "and upper-bound duration without starting it. Balancer limits and profile presets are read from " +
+ "the local Ozone configuration (including ozone-site.xml), datanode usage is fetched from SCM.",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class ContainerBalancerDryRunSubcommand extends ScmSubcommand {
+
+ private static final double PLANNING_ITERATION_BUFFER = 1.3d;
+
+ @CommandLine.Mixin
+ private ContainerBalancerConfigOptions configOptions;
+
+ @CommandLine.ArgGroup(exclusive = true, multiplicity = "0..1")
+ private ProfileSelection profileSelection;
+
+ @Override
+ public void execute(ScmClient scmClient) throws IOException {
+
+ List<DatanodeUsageInfoProto> nodes = scmClient.getDatanodeUsageInfo(true, Integer.MAX_VALUE);
+ if (nodes == null || nodes.isEmpty()) {
+ throw new IOException("No datanode usage information available from SCM.");
+ }
+
+ OzoneConfiguration conf = getOzoneConf();
+ ContainerBalancerAdvisor.AdvisorRequest request = buildRequest(nodes);
+ List<ContainerBalancerEstimation> estimations;
+ try {
+ estimations = ContainerBalancerAdvisor.estimateDryRun(conf, request);
+ } catch (IllegalArgumentException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+
+ boolean anySucceeded = false;
+ for (ContainerBalancerEstimation result : estimations) {
+ out().printf("Profile: %s%n", result.getProfile().name());
+ printBasedOn(result);
+ if (result.succeeded()) {
+ anySucceeded = true;
+ printEstimation(result);
+ } else {
+ out().printf(" Estimation failed: %s%n%n", result.getFailureMessage());
+ }
+ }
+ if (!anySucceeded) {
+ throw new IOException(estimations.get(0).getFailureMessage());
+ }
+ }
+
+ private ContainerBalancerAdvisor.AdvisorRequest buildRequest(List<DatanodeUsageInfoProto> nodes) throws IOException {
+ ContainerBalancerAdvisor.AdvisorRequest request = new ContainerBalancerAdvisor.AdvisorRequest().setNodes(nodes);
+ configOptions.applyToDryRunRequest(request);
+
+ if (profileSelection != null) {
+ if (profileSelection.allProfiles) {
+ request.setAllProfiles(true);
+ } else {
+ request.setProfile(parseProfile(profileSelection.profileName.get()));
+ }
+ }
+ return request;
+ }
+
+ private static ContainerBalancerProfile parseProfile(String name) throws IOException {
+ try {
+ return ContainerBalancerProfile.valueOf(name.trim().toUpperCase(Locale.ENGLISH));
+ } catch (IllegalArgumentException e) {
+ throw new IOException("Invalid profile: " + name + ". Expected SLOW, MEDIUM, or FAST.");
+ }
+ }
+
+ private void printBasedOn(ContainerBalancerEstimation estimation) {
+ long moveTimeoutMinutes = Math.round(estimation.getMoveTimeoutMillis() / 60000d);
+ long balancingIntervalMinutes = Math.round(estimation.getBalancingIntervalMillis() / 60000d);
+ out().println(" Based on:");
+ out().printf(Locale.ENGLISH, " Datanode involvement: %d%%%n",
+ estimation.getMaxDatanodesPercentage());
+ out().printf(" Max entering target: %s / node%n", byteDesc(estimation.getMaxSizeEnteringTarget()));
+ out().printf(" Max leaving source: %s / node%n", byteDesc(estimation.getMaxSizeLeavingSource()));
+ out().printf(" Max per iteration: %s%n", byteDesc(estimation.getMaxSizeToMovePerIteration()));
+ out().printf(" Move timeout: %d min%n", moveTimeoutMinutes);
+ out().printf(" Balancing interval: %d min%n", balancingIntervalMinutes);
+ }
+
+ private void printEstimation(ContainerBalancerEstimation estimation) {
Review Comment:
Would it be better to print the threshold in the output too? Dry-run uses -t (or the config default) to give the estimated bytes to move, but the report doesn’t show it. similar to the assessment and recommendation command.
##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerDryRunSubcommand.java:
##########
@@ -0,0 +1,166 @@
+/*
+ * 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.hadoop.hdds.scm.cli;
+
+import static org.apache.hadoop.util.StringUtils.byteDesc;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerAdvisor;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerEstimation;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerProfile;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Estimates container balancer bytes to move, per iteration bytes, estimated iterations, estimated duration
+ * without starting the balancer.
+ */
+@Command(
+ name = "dry-run",
+ description = "Estimate container balancer bytes to move, iterations, per iteration bytes " +
+ "and upper-bound duration without starting it. Balancer limits and profile presets are read from " +
+ "the local Ozone configuration (including ozone-site.xml), datanode usage is fetched from SCM.",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class ContainerBalancerDryRunSubcommand extends ScmSubcommand {
+
+ private static final double PLANNING_ITERATION_BUFFER = 1.3d;
+
+ @CommandLine.Mixin
+ private ContainerBalancerConfigOptions configOptions;
+
+ @CommandLine.ArgGroup(exclusive = true, multiplicity = "0..1")
+ private ProfileSelection profileSelection;
+
+ @Override
+ public void execute(ScmClient scmClient) throws IOException {
+
+ List<DatanodeUsageInfoProto> nodes = scmClient.getDatanodeUsageInfo(true, Integer.MAX_VALUE);
+ if (nodes == null || nodes.isEmpty()) {
+ throw new IOException("No datanode usage information available from SCM.");
+ }
+
+ OzoneConfiguration conf = getOzoneConf();
+ ContainerBalancerAdvisor.AdvisorRequest request = buildRequest(nodes);
+ List<ContainerBalancerEstimation> estimations;
+ try {
+ estimations = ContainerBalancerAdvisor.estimateDryRun(conf, request);
+ } catch (IllegalArgumentException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+
+ boolean anySucceeded = false;
+ for (ContainerBalancerEstimation result : estimations) {
Review Comment:
when no profile is specified would it be better to specify that we're going with default?
something like :
```
No profile specified using the default.
Profile: MEDIUM
...
```
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Dry run command for container balancer with profiles [ozone]
Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai commented on code in PR #11255:
URL: https://github.com/apache/ozone/pull/11255#discussion_r4093279140
##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerDryRunSubcommand.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.hadoop.hdds.scm.cli;
+
+import static org.apache.hadoop.util.StringUtils.byteDesc;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerAdvisor;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerEstimation;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerProfile;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Estimates container balancer bytes to move, per iteration bytes, estimated iterations, estimated duration
+ * without starting the balancer.
+ */
+@Command(
+ name = "dry-run",
Review Comment:
Would `plan` or `estimate` be a better name?
##########
hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancerAdvisor.java:
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.hadoop.hdds.scm.container.balancer;
+
+import static org.apache.hadoop.ozone.ClientVersion.DEFAULT_VERSION;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link ContainerBalancerAdvisor} dry-run estimation. */
+public final class TestContainerBalancerAdvisor {
+
+ @Test
+ void testComputePerIterationBytesNeverExceedsBytesToMove() {
+ int[] involved = {3, 3};
+ long bytesToMove = 50L * OzoneConsts.GB;
+
+ assertEquals(bytesToMove, ContainerBalancerAdvisor.computePerIterationBytes(
+ bytesToMove,
+ 500L * OzoneConsts.GB,
+ 26 * OzoneConsts.GB,
+ 26 * OzoneConsts.GB,
+ involved));
+ }
+
+ @Test
+ void testEstimateDryRunDefaultReturnsMediumProfile() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ List<ContainerBalancerEstimation> results = ContainerBalancerAdvisor.estimateDryRun(
+ conf,
+ new ContainerBalancerAdvisor.AdvisorRequest().setNodes(buildCluster(70, 14, 14)));
+
+ assertEquals(1, results.size());
+ ContainerBalancerEstimation estimation = results.get(0);
+ assertTrue(estimation.succeeded());
+ assertEquals(ContainerBalancerProfile.MEDIUM, estimation.getProfile());
+ assertTrue(estimation.getBytesToMove() > 0);
Review Comment:
nit: please use `assertThat` (`.isPositive()` here) for comparisons and other assertions (e.g. `String.contains()`) where a simple `expected: true but was: false` is less helpful
see HDDS-9951
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Estimate subcommand for container balancer with profiles [ozone]
Posted by "ashishkumar50 (via GitHub)" <gi...@apache.org>.
ashishkumar50 commented on PR #11255:
URL: https://github.com/apache/ozone/pull/11255#issuecomment-5831643653
@sreejasahithi Thanks for the patch, @adoroszlai @sravani-revuri for the review.
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Dry run command for container balancer with profiles [ozone]
Posted by "ashishkumar50 (via GitHub)" <gi...@apache.org>.
ashishkumar50 commented on code in PR #11255:
URL: https://github.com/apache/ozone/pull/11255#discussion_r4079597506
##########
hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerAdvisor.java:
##########
@@ -0,0 +1,457 @@
+/*
+ * 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.hadoop.hdds.scm.container.balancer;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.conf.StorageUnit;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+
+/**
+ * Orchestrates cluster analysis, estimation and recommendation for container balancer.
+ */
+public final class ContainerBalancerAdvisor {
+
+ private static final long MIN_DELETE_PHASE_MILLIS = Duration.ofMinutes(9).toMillis();
+ private static final String DATANODE_OFFSET_KEY =
+ "hdds.scm.replication.event.timeout.datanode.offset";
+
+ private ContainerBalancerAdvisor() {
+ }
+
+ /**
+ * Estimates per-iteration size, iterations and duration for one or more balancer profiles.
+ *
+ * If {@link AdvisorRequest#allProfiles} is true, returns SLOW, MEDIUM, and FAST.
+ * If profile is set, returns a result for that profile only.
+ * Otherwise returns MEDIUM only.
+ * Per-profile validation failures are returned with {@link ContainerBalancerEstimation#succeeded()} false
+ * instead of aborting other profiles.
+ */
+ public static List<ContainerBalancerEstimation> estimateDryRun(OzoneConfiguration conf, AdvisorRequest request) {
+ Objects.requireNonNull(conf, "conf");
+ Objects.requireNonNull(request, "request");
+ List<DatanodeUsageInfoProto> nodes = Objects.requireNonNull(request.nodes, "nodes");
+
+ ContainerBalancerConfiguration balancerConfig = conf.getObject(ContainerBalancerConfiguration.class);
+
+ double thresholdPercent = request.thresholdPercent != null
+ ? request.thresholdPercent
+ : balancerConfig.getThreshold();
+ validateThresholdPercent(thresholdPercent);
+ double thresholdRatio = thresholdPercent / 100.0;
+ Set<String> includeNodes = request.includeNodes != null
+ ? request.includeNodes
+ : balancerConfig.getIncludeNodes();
+ Set<String> excludeNodes = request.excludeNodes != null
+ ? request.excludeNodes
+ : balancerConfig.getExcludeNodes();
+
+ ContainerBalancerClusterSnapshot snapshot = ContainerBalancerClusterAnalyzer.analyze(nodes, thresholdRatio,
+ includeNodes, excludeNodes);
+ validateSnapshotForEstimation(snapshot, conf);
+
+ List<ContainerBalancerProfile> profiles = selectProfiles(request);
+ List<ContainerBalancerEstimation> estimations = new ArrayList<>(profiles.size());
+ for (ContainerBalancerProfile profile : profiles) {
+ estimations.add(estimateForProfile(conf, request, profile, snapshot, balancerConfig, thresholdPercent));
+ }
+ return Collections.unmodifiableList(estimations);
+ }
+
+ private static ContainerBalancerEstimation estimateForProfile(OzoneConfiguration conf, AdvisorRequest request,
+ ContainerBalancerProfile profile, ContainerBalancerClusterSnapshot snapshot,
+ ContainerBalancerConfiguration balancerConfig, double thresholdPercent) {
+
+ boolean userProvidedMaxDatanodesPercentage =
+ request.maxDatanodesPercentageToInvolvePerIteration != null;
+ int maxDatanodesPercentage = userProvidedMaxDatanodesPercentage
+ ? request.maxDatanodesPercentageToInvolvePerIteration
+ : profile.getDatanodesMaxPercentage(balancerConfig);
+ long maxSizeEnteringTarget = request.maxSizeEnteringTarget != null
+ ? request.maxSizeEnteringTarget
+ : profile.getMaxSizeEnteringTarget(balancerConfig);
+ long maxSizeLeavingSource = request.maxSizeLeavingSource != null
+ ? request.maxSizeLeavingSource
+ : profile.getMaxSizeLeavingSource(balancerConfig);
+ long maxSizeToMovePerIteration = request.maxSizeToMovePerIteration != null
+ ? request.maxSizeToMovePerIteration
+ : balancerConfig.getMaxSizeToMovePerIteration();
+ long moveTimeoutMillis = request.moveTimeoutMillis != null
+ ? request.moveTimeoutMillis
+ : balancerConfig.getMoveTimeout().toMillis();
+ long moveReplicationTimeoutMillis = request.moveReplicationTimeoutMillis != null
+ ? request.moveReplicationTimeoutMillis
+ : balancerConfig.getMoveReplicationTimeout().toMillis();
+ long balancingIntervalMillis = request.balancingIntervalMillis != null
+ ? request.balancingIntervalMillis
+ : balancerConfig.getBalancingInterval().toMillis();
+
+ ContainerBalancerEstimation.Builder builder = ContainerBalancerEstimation.newBuilder()
+ .setProfile(profile)
+ .setThresholdPercent(thresholdPercent)
+ .setMaxSizeEnteringTarget(maxSizeEnteringTarget)
+ .setMaxSizeLeavingSource(maxSizeLeavingSource)
+ .setMaxSizeToMovePerIteration(maxSizeToMovePerIteration)
+ .setMoveTimeoutMillis(moveTimeoutMillis)
+ .setBalancingIntervalMillis(balancingIntervalMillis);
+
+ try {
+ validateMaxDatanodesPercentageToInvolvePerIteration(maxDatanodesPercentage);
+ validateMoveTimeouts(conf, moveReplicationTimeoutMillis, moveTimeoutMillis);
+ validateBalancingIntervalMillis(balancingIntervalMillis);
+ validateResolvedMoveLimits(conf, maxSizeEnteringTarget, maxSizeLeavingSource, maxSizeToMovePerIteration);
+
+ int eligibleDatanodeCount = snapshot.getTotalEligibleDatanodes();
+ int maxInvolved = ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ maxDatanodesPercentage / 100d, eligibleDatanodeCount);
+
+ if (!userProvidedMaxDatanodesPercentage && maxInvolved < 2) {
+ int maxProfileDatanodesPercentage =
+ balancerConfig.getProfileDatanodesMaxPercentage(ContainerBalancerProfile.FAST);
+ maxDatanodesPercentage = minimumPercentForAtLeastTwoNodes(
+ eligibleDatanodeCount, maxDatanodesPercentage, maxProfileDatanodesPercentage);
+ maxInvolved = ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ maxDatanodesPercentage / 100d, eligibleDatanodeCount);
+ }
+
+ if (maxInvolved < 2) {
+ throw new IllegalArgumentException(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.",
+ maxDatanodesPercentage, maxInvolved, eligibleDatanodeCount));
+ }
+
+ int[] involved = computeInvolvedDatanodeCounts(snapshot.getSourceCount(), snapshot.getTargetCount(), maxInvolved);
+
+ long bytesToMove = snapshot.getBytesToMove();
+ long perIterationBytes = computePerIterationBytes(
+ bytesToMove,
+ maxSizeToMovePerIteration,
+ maxSizeLeavingSource,
+ maxSizeEnteringTarget,
+ involved);
+ long estimatedIterations = computeEstimatedIterations(perIterationBytes, bytesToMove);
+ long cycleTimeMillis = computeCycleTimeMillis(moveTimeoutMillis, balancingIntervalMillis);
+ long estimatedDurationMillis = estimatedIterations * cycleTimeMillis;
+
+ return builder
+ .setMaxDatanodesPercentage(maxDatanodesPercentage)
+ .setBytesToMove(bytesToMove)
+ .setPerIterationBytes(perIterationBytes)
+ .setEstimatedIterations(estimatedIterations)
+ .setEstimatedDurationMillis(estimatedDurationMillis)
+ .build();
+ } catch (IllegalArgumentException e) {
+ return builder
+ .setMaxDatanodesPercentage(maxDatanodesPercentage)
+ .setFailureMessage(e.getMessage())
+ .build();
+ }
+ }
+
+ /** Raises datanode involvement percent until at least two datanodes can be involved. */
+ static int minimumPercentForAtLeastTwoNodes(
+ int eligibleDatanodeCount, int startPercent, int maxPercent) {
+ for (int percent = startPercent; percent <= maxPercent; percent++) {
+ if (ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ percent / 100d, eligibleDatanodeCount) >= 2) {
+ return percent;
+ }
+ }
+ return maxPercent;
+ }
+
+ /**
+ * Estimated bytes moved in one iteration: minimum of global cap, source cap,
+ * target cap, and total bytes to move.
+ */
+ static long computePerIterationBytes(
+ long bytesToMove,
+ long maxSizeToMovePerIteration,
+ long maxSizeLeavingSource,
+ long maxSizeEnteringTarget,
+ int[] involved) {
+
+ long fromLeaving = involved[0] * maxSizeLeavingSource;
+ long fromEntering = involved[1] * maxSizeEnteringTarget;
+
+ return minPositive(
+ maxSizeToMovePerIteration,
+ fromLeaving,
+ fromEntering,
+ bytesToMove);
+ }
+
+ static long computeCycleTimeMillis(long moveTimeoutMillis, long balancingIntervalMillis) {
+ return moveTimeoutMillis + balancingIntervalMillis;
+ }
+
+ static long computeEstimatedIterations(long perIterationBytes, long bytesToMove) {
+ if (perIterationBytes <= 0) {
+ throw new IllegalArgumentException("Per-iteration move size must be positive.");
+ }
+ return (long) Math.ceil(bytesToMove / (double) perIterationBytes);
+ }
+
+ /**
+ * Estimated source and target datanode counts for one iteration (50/50 split heuristic).
+ *
+ * @return {@code [involvedSources, involvedTargets]}
+ */
+ static int[] computeInvolvedDatanodeCounts(
+ int sourceCount,
+ int targetCount,
+ int maxInvolved) {
+ if (sourceCount <= 0 || targetCount <= 0 || maxInvolved < 2) {
+ return new int[] {0, 0};
+ }
+
+ int sEff = Math.min(sourceCount, (maxInvolved + 1) / 2);
+ int tEff = Math.min(targetCount, maxInvolved / 2);
+ return new int[] {sEff, tEff};
+ }
+
+ private static long minPositive(long... values) {
+ long result = Long.MAX_VALUE;
+ for (long value : values) {
+ if (value > 0 && value < result) {
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ private static void validateSnapshotForEstimation(ContainerBalancerClusterSnapshot snapshot,
+ OzoneConfiguration conf) {
+ long containerSizeBytes = (long) conf.getStorageSize(
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE,
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE_DEFAULT,
+ StorageUnit.BYTES);
+ if (snapshot.getSourceCount() < 1) {
+ throw new IllegalArgumentException("No over-utilized datanodes (sources) found.");
+ }
+ if (snapshot.getTargetCount() < 1) {
+ throw new IllegalArgumentException("No under-utilized datanodes (targets) found.");
+ }
+ if (snapshot.getBytesToMove() <= 0) {
+ throw new IllegalArgumentException("No bytes to move.");
+ }
+ if (snapshot.getBytesToMove() < containerSizeBytes) {
+ throw new IllegalArgumentException(
+ "Bytes to move (" + snapshot.getBytesToMove()
+ + ") is less than container size (" + containerSizeBytes + ").");
Review Comment:
The real balancer selects containers by `actual container.getUsedBytes()`, so it can still move smaller containers. We should not throw exception here.
##########
hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerAdvisor.java:
##########
@@ -0,0 +1,457 @@
+/*
+ * 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.hadoop.hdds.scm.container.balancer;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.conf.StorageUnit;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+
+/**
+ * Orchestrates cluster analysis, estimation and recommendation for container balancer.
+ */
+public final class ContainerBalancerAdvisor {
+
+ private static final long MIN_DELETE_PHASE_MILLIS = Duration.ofMinutes(9).toMillis();
+ private static final String DATANODE_OFFSET_KEY =
+ "hdds.scm.replication.event.timeout.datanode.offset";
+
+ private ContainerBalancerAdvisor() {
+ }
+
+ /**
+ * Estimates per-iteration size, iterations and duration for one or more balancer profiles.
+ *
+ * If {@link AdvisorRequest#allProfiles} is true, returns SLOW, MEDIUM, and FAST.
+ * If profile is set, returns a result for that profile only.
+ * Otherwise returns MEDIUM only.
+ * Per-profile validation failures are returned with {@link ContainerBalancerEstimation#succeeded()} false
+ * instead of aborting other profiles.
+ */
+ public static List<ContainerBalancerEstimation> estimateDryRun(OzoneConfiguration conf, AdvisorRequest request) {
+ Objects.requireNonNull(conf, "conf");
+ Objects.requireNonNull(request, "request");
+ List<DatanodeUsageInfoProto> nodes = Objects.requireNonNull(request.nodes, "nodes");
+
+ ContainerBalancerConfiguration balancerConfig = conf.getObject(ContainerBalancerConfiguration.class);
+
+ double thresholdPercent = request.thresholdPercent != null
+ ? request.thresholdPercent
+ : balancerConfig.getThreshold();
+ validateThresholdPercent(thresholdPercent);
+ double thresholdRatio = thresholdPercent / 100.0;
+ Set<String> includeNodes = request.includeNodes != null
+ ? request.includeNodes
+ : balancerConfig.getIncludeNodes();
+ Set<String> excludeNodes = request.excludeNodes != null
+ ? request.excludeNodes
+ : balancerConfig.getExcludeNodes();
+
+ ContainerBalancerClusterSnapshot snapshot = ContainerBalancerClusterAnalyzer.analyze(nodes, thresholdRatio,
+ includeNodes, excludeNodes);
+ validateSnapshotForEstimation(snapshot, conf);
+
+ List<ContainerBalancerProfile> profiles = selectProfiles(request);
+ List<ContainerBalancerEstimation> estimations = new ArrayList<>(profiles.size());
+ for (ContainerBalancerProfile profile : profiles) {
+ estimations.add(estimateForProfile(conf, request, profile, snapshot, balancerConfig, thresholdPercent));
+ }
+ return Collections.unmodifiableList(estimations);
+ }
+
+ private static ContainerBalancerEstimation estimateForProfile(OzoneConfiguration conf, AdvisorRequest request,
+ ContainerBalancerProfile profile, ContainerBalancerClusterSnapshot snapshot,
+ ContainerBalancerConfiguration balancerConfig, double thresholdPercent) {
+
+ boolean userProvidedMaxDatanodesPercentage =
+ request.maxDatanodesPercentageToInvolvePerIteration != null;
+ int maxDatanodesPercentage = userProvidedMaxDatanodesPercentage
+ ? request.maxDatanodesPercentageToInvolvePerIteration
+ : profile.getDatanodesMaxPercentage(balancerConfig);
+ long maxSizeEnteringTarget = request.maxSizeEnteringTarget != null
+ ? request.maxSizeEnteringTarget
+ : profile.getMaxSizeEnteringTarget(balancerConfig);
+ long maxSizeLeavingSource = request.maxSizeLeavingSource != null
+ ? request.maxSizeLeavingSource
+ : profile.getMaxSizeLeavingSource(balancerConfig);
+ long maxSizeToMovePerIteration = request.maxSizeToMovePerIteration != null
+ ? request.maxSizeToMovePerIteration
+ : balancerConfig.getMaxSizeToMovePerIteration();
+ long moveTimeoutMillis = request.moveTimeoutMillis != null
+ ? request.moveTimeoutMillis
+ : balancerConfig.getMoveTimeout().toMillis();
+ long moveReplicationTimeoutMillis = request.moveReplicationTimeoutMillis != null
+ ? request.moveReplicationTimeoutMillis
+ : balancerConfig.getMoveReplicationTimeout().toMillis();
+ long balancingIntervalMillis = request.balancingIntervalMillis != null
+ ? request.balancingIntervalMillis
+ : balancerConfig.getBalancingInterval().toMillis();
+
+ ContainerBalancerEstimation.Builder builder = ContainerBalancerEstimation.newBuilder()
+ .setProfile(profile)
+ .setThresholdPercent(thresholdPercent)
+ .setMaxSizeEnteringTarget(maxSizeEnteringTarget)
+ .setMaxSizeLeavingSource(maxSizeLeavingSource)
+ .setMaxSizeToMovePerIteration(maxSizeToMovePerIteration)
+ .setMoveTimeoutMillis(moveTimeoutMillis)
+ .setBalancingIntervalMillis(balancingIntervalMillis);
+
+ try {
+ validateMaxDatanodesPercentageToInvolvePerIteration(maxDatanodesPercentage);
+ validateMoveTimeouts(conf, moveReplicationTimeoutMillis, moveTimeoutMillis);
+ validateBalancingIntervalMillis(balancingIntervalMillis);
+ validateResolvedMoveLimits(conf, maxSizeEnteringTarget, maxSizeLeavingSource, maxSizeToMovePerIteration);
+
+ int eligibleDatanodeCount = snapshot.getTotalEligibleDatanodes();
+ int maxInvolved = ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ maxDatanodesPercentage / 100d, eligibleDatanodeCount);
+
+ if (!userProvidedMaxDatanodesPercentage && maxInvolved < 2) {
+ int maxProfileDatanodesPercentage =
+ balancerConfig.getProfileDatanodesMaxPercentage(ContainerBalancerProfile.FAST);
+ maxDatanodesPercentage = minimumPercentForAtLeastTwoNodes(
+ eligibleDatanodeCount, maxDatanodesPercentage, maxProfileDatanodesPercentage);
+ maxInvolved = ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ maxDatanodesPercentage / 100d, eligibleDatanodeCount);
+ }
+
+ if (maxInvolved < 2) {
+ throw new IllegalArgumentException(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.",
+ maxDatanodesPercentage, maxInvolved, eligibleDatanodeCount));
+ }
+
+ int[] involved = computeInvolvedDatanodeCounts(snapshot.getSourceCount(), snapshot.getTargetCount(), maxInvolved);
+
+ long bytesToMove = snapshot.getBytesToMove();
+ long perIterationBytes = computePerIterationBytes(
+ bytesToMove,
+ maxSizeToMovePerIteration,
+ maxSizeLeavingSource,
+ maxSizeEnteringTarget,
+ involved);
+ long estimatedIterations = computeEstimatedIterations(perIterationBytes, bytesToMove);
+ long cycleTimeMillis = computeCycleTimeMillis(moveTimeoutMillis, balancingIntervalMillis);
+ long estimatedDurationMillis = estimatedIterations * cycleTimeMillis;
+
+ return builder
+ .setMaxDatanodesPercentage(maxDatanodesPercentage)
+ .setBytesToMove(bytesToMove)
+ .setPerIterationBytes(perIterationBytes)
+ .setEstimatedIterations(estimatedIterations)
+ .setEstimatedDurationMillis(estimatedDurationMillis)
+ .build();
+ } catch (IllegalArgumentException e) {
+ return builder
+ .setMaxDatanodesPercentage(maxDatanodesPercentage)
+ .setFailureMessage(e.getMessage())
+ .build();
+ }
+ }
+
+ /** Raises datanode involvement percent until at least two datanodes can be involved. */
+ static int minimumPercentForAtLeastTwoNodes(
+ int eligibleDatanodeCount, int startPercent, int maxPercent) {
+ for (int percent = startPercent; percent <= maxPercent; percent++) {
+ if (ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ percent / 100d, eligibleDatanodeCount) >= 2) {
+ return percent;
+ }
+ }
+ return maxPercent;
+ }
+
+ /**
+ * Estimated bytes moved in one iteration: minimum of global cap, source cap,
+ * target cap, and total bytes to move.
+ */
+ static long computePerIterationBytes(
+ long bytesToMove,
+ long maxSizeToMovePerIteration,
+ long maxSizeLeavingSource,
+ long maxSizeEnteringTarget,
+ int[] involved) {
+
+ long fromLeaving = involved[0] * maxSizeLeavingSource;
+ long fromEntering = involved[1] * maxSizeEnteringTarget;
+
+ return minPositive(
+ maxSizeToMovePerIteration,
+ fromLeaving,
+ fromEntering,
+ bytesToMove);
+ }
+
+ static long computeCycleTimeMillis(long moveTimeoutMillis, long balancingIntervalMillis) {
+ return moveTimeoutMillis + balancingIntervalMillis;
+ }
+
+ static long computeEstimatedIterations(long perIterationBytes, long bytesToMove) {
+ if (perIterationBytes <= 0) {
+ throw new IllegalArgumentException("Per-iteration move size must be positive.");
+ }
+ return (long) Math.ceil(bytesToMove / (double) perIterationBytes);
+ }
+
+ /**
+ * Estimated source and target datanode counts for one iteration (50/50 split heuristic).
+ *
+ * @return {@code [involvedSources, involvedTargets]}
+ */
+ static int[] computeInvolvedDatanodeCounts(
+ int sourceCount,
+ int targetCount,
+ int maxInvolved) {
+ if (sourceCount <= 0 || targetCount <= 0 || maxInvolved < 2) {
+ return new int[] {0, 0};
+ }
+
+ int sEff = Math.min(sourceCount, (maxInvolved + 1) / 2);
+ int tEff = Math.min(targetCount, maxInvolved / 2);
+ return new int[] {sEff, tEff};
+ }
+
+ private static long minPositive(long... values) {
+ long result = Long.MAX_VALUE;
+ for (long value : values) {
+ if (value > 0 && value < result) {
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ private static void validateSnapshotForEstimation(ContainerBalancerClusterSnapshot snapshot,
+ OzoneConfiguration conf) {
+ long containerSizeBytes = (long) conf.getStorageSize(
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE,
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE_DEFAULT,
+ StorageUnit.BYTES);
+ if (snapshot.getSourceCount() < 1) {
+ throw new IllegalArgumentException("No over-utilized datanodes (sources) found.");
+ }
+ if (snapshot.getTargetCount() < 1) {
+ throw new IllegalArgumentException("No under-utilized datanodes (targets) found.");
+ }
+ if (snapshot.getBytesToMove() <= 0) {
+ throw new IllegalArgumentException("No bytes to move.");
+ }
+ if (snapshot.getBytesToMove() < containerSizeBytes) {
+ throw new IllegalArgumentException(
+ "Bytes to move (" + snapshot.getBytesToMove()
+ + ") is less than container size (" + containerSizeBytes + ").");
+ }
+ if (snapshot.getTotalEligibleDatanodes() < 2) {
+ throw new IllegalArgumentException(String.format(
+ "Container Balancer found %d eligible datanode(s) but requires at least 2.",
+ snapshot.getTotalEligibleDatanodes()));
+ }
+ }
+
+ private static void validateResolvedMoveLimits(OzoneConfiguration conf, long maxSizeEnteringTarget,
+ long maxSizeLeavingSource, long maxSizeToMovePerIteration) {
+ long containerSizeBytes = (long) conf.getStorageSize(
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE,
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE_DEFAULT,
+ StorageUnit.BYTES);
+
+ if (maxSizeEnteringTarget <= 0) {
+ throw new IllegalArgumentException("Max Size Entering Target must be greater than zero.");
+ }
+ if (maxSizeEnteringTarget <= containerSizeBytes) {
+ throw new IllegalArgumentException(
+ "max-size-entering-target must be greater than ozone.scm.container.size ("
+ + containerSizeBytes + " bytes).");
+ }
+ if (maxSizeLeavingSource <= 0) {
+ throw new IllegalArgumentException("Max Size Leaving Source must be greater than zero.");
+ }
+ if (maxSizeLeavingSource <= containerSizeBytes) {
+ throw new IllegalArgumentException(
+ "max-size-leaving-source must be greater than ozone.scm.container.size ("
+ + containerSizeBytes + " bytes).");
+ }
+ if (maxSizeToMovePerIteration <= 0) {
+ throw new IllegalArgumentException("Max Size To Move Per Iteration In GB must be positive.");
+ }
+ if (maxSizeEnteringTarget > maxSizeToMovePerIteration) {
+ throw new IllegalArgumentException(
+ "max-size-entering-target must be less than or equal to "
+ + "max-size-to-move-per-iteration.");
+ }
+ if (maxSizeLeavingSource > maxSizeToMovePerIteration) {
+ throw new IllegalArgumentException(
+ "max-size-leaving-source must be less than or equal to "
+ + "max-size-to-move-per-iteration.");
+ }
+ }
+
+ private static void validateMoveTimeouts(OzoneConfiguration conf, long moveReplicationTimeoutMillis,
+ long moveTimeoutMillis) {
+ if (moveTimeoutMillis <= 0) {
+ throw new IllegalArgumentException("Move Timeout must be greater than zero.");
+ }
+ if (moveReplicationTimeoutMillis <= 0) {
+ throw new IllegalArgumentException("Move Replication Timeout must be greater than zero.");
Review Comment:
Same merge multiple condition into one.
##########
hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerAdvisor.java:
##########
@@ -0,0 +1,457 @@
+/*
+ * 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.hadoop.hdds.scm.container.balancer;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.conf.StorageUnit;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+
+/**
+ * Orchestrates cluster analysis, estimation and recommendation for container balancer.
+ */
+public final class ContainerBalancerAdvisor {
+
+ private static final long MIN_DELETE_PHASE_MILLIS = Duration.ofMinutes(9).toMillis();
+ private static final String DATANODE_OFFSET_KEY =
+ "hdds.scm.replication.event.timeout.datanode.offset";
+
+ private ContainerBalancerAdvisor() {
+ }
+
+ /**
+ * Estimates per-iteration size, iterations and duration for one or more balancer profiles.
+ *
+ * If {@link AdvisorRequest#allProfiles} is true, returns SLOW, MEDIUM, and FAST.
+ * If profile is set, returns a result for that profile only.
+ * Otherwise returns MEDIUM only.
+ * Per-profile validation failures are returned with {@link ContainerBalancerEstimation#succeeded()} false
+ * instead of aborting other profiles.
+ */
+ public static List<ContainerBalancerEstimation> estimateDryRun(OzoneConfiguration conf, AdvisorRequest request) {
+ Objects.requireNonNull(conf, "conf");
+ Objects.requireNonNull(request, "request");
+ List<DatanodeUsageInfoProto> nodes = Objects.requireNonNull(request.nodes, "nodes");
+
+ ContainerBalancerConfiguration balancerConfig = conf.getObject(ContainerBalancerConfiguration.class);
+
+ double thresholdPercent = request.thresholdPercent != null
+ ? request.thresholdPercent
+ : balancerConfig.getThreshold();
+ validateThresholdPercent(thresholdPercent);
+ double thresholdRatio = thresholdPercent / 100.0;
+ Set<String> includeNodes = request.includeNodes != null
+ ? request.includeNodes
+ : balancerConfig.getIncludeNodes();
+ Set<String> excludeNodes = request.excludeNodes != null
+ ? request.excludeNodes
+ : balancerConfig.getExcludeNodes();
+
+ ContainerBalancerClusterSnapshot snapshot = ContainerBalancerClusterAnalyzer.analyze(nodes, thresholdRatio,
+ includeNodes, excludeNodes);
+ validateSnapshotForEstimation(snapshot, conf);
+
+ List<ContainerBalancerProfile> profiles = selectProfiles(request);
+ List<ContainerBalancerEstimation> estimations = new ArrayList<>(profiles.size());
+ for (ContainerBalancerProfile profile : profiles) {
+ estimations.add(estimateForProfile(conf, request, profile, snapshot, balancerConfig, thresholdPercent));
+ }
+ return Collections.unmodifiableList(estimations);
+ }
+
+ private static ContainerBalancerEstimation estimateForProfile(OzoneConfiguration conf, AdvisorRequest request,
+ ContainerBalancerProfile profile, ContainerBalancerClusterSnapshot snapshot,
+ ContainerBalancerConfiguration balancerConfig, double thresholdPercent) {
+
+ boolean userProvidedMaxDatanodesPercentage =
+ request.maxDatanodesPercentageToInvolvePerIteration != null;
+ int maxDatanodesPercentage = userProvidedMaxDatanodesPercentage
+ ? request.maxDatanodesPercentageToInvolvePerIteration
+ : profile.getDatanodesMaxPercentage(balancerConfig);
+ long maxSizeEnteringTarget = request.maxSizeEnteringTarget != null
+ ? request.maxSizeEnteringTarget
+ : profile.getMaxSizeEnteringTarget(balancerConfig);
+ long maxSizeLeavingSource = request.maxSizeLeavingSource != null
+ ? request.maxSizeLeavingSource
+ : profile.getMaxSizeLeavingSource(balancerConfig);
+ long maxSizeToMovePerIteration = request.maxSizeToMovePerIteration != null
+ ? request.maxSizeToMovePerIteration
+ : balancerConfig.getMaxSizeToMovePerIteration();
+ long moveTimeoutMillis = request.moveTimeoutMillis != null
+ ? request.moveTimeoutMillis
+ : balancerConfig.getMoveTimeout().toMillis();
+ long moveReplicationTimeoutMillis = request.moveReplicationTimeoutMillis != null
+ ? request.moveReplicationTimeoutMillis
+ : balancerConfig.getMoveReplicationTimeout().toMillis();
+ long balancingIntervalMillis = request.balancingIntervalMillis != null
+ ? request.balancingIntervalMillis
+ : balancerConfig.getBalancingInterval().toMillis();
+
+ ContainerBalancerEstimation.Builder builder = ContainerBalancerEstimation.newBuilder()
+ .setProfile(profile)
+ .setThresholdPercent(thresholdPercent)
+ .setMaxSizeEnteringTarget(maxSizeEnteringTarget)
+ .setMaxSizeLeavingSource(maxSizeLeavingSource)
+ .setMaxSizeToMovePerIteration(maxSizeToMovePerIteration)
+ .setMoveTimeoutMillis(moveTimeoutMillis)
+ .setBalancingIntervalMillis(balancingIntervalMillis);
+
+ try {
+ validateMaxDatanodesPercentageToInvolvePerIteration(maxDatanodesPercentage);
+ validateMoveTimeouts(conf, moveReplicationTimeoutMillis, moveTimeoutMillis);
+ validateBalancingIntervalMillis(balancingIntervalMillis);
+ validateResolvedMoveLimits(conf, maxSizeEnteringTarget, maxSizeLeavingSource, maxSizeToMovePerIteration);
+
+ int eligibleDatanodeCount = snapshot.getTotalEligibleDatanodes();
+ int maxInvolved = ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ maxDatanodesPercentage / 100d, eligibleDatanodeCount);
+
+ if (!userProvidedMaxDatanodesPercentage && maxInvolved < 2) {
+ int maxProfileDatanodesPercentage =
+ balancerConfig.getProfileDatanodesMaxPercentage(ContainerBalancerProfile.FAST);
+ maxDatanodesPercentage = minimumPercentForAtLeastTwoNodes(
+ eligibleDatanodeCount, maxDatanodesPercentage, maxProfileDatanodesPercentage);
+ maxInvolved = ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ maxDatanodesPercentage / 100d, eligibleDatanodeCount);
+ }
+
+ if (maxInvolved < 2) {
+ throw new IllegalArgumentException(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.",
+ maxDatanodesPercentage, maxInvolved, eligibleDatanodeCount));
+ }
+
+ int[] involved = computeInvolvedDatanodeCounts(snapshot.getSourceCount(), snapshot.getTargetCount(), maxInvolved);
+
+ long bytesToMove = snapshot.getBytesToMove();
+ long perIterationBytes = computePerIterationBytes(
+ bytesToMove,
+ maxSizeToMovePerIteration,
+ maxSizeLeavingSource,
+ maxSizeEnteringTarget,
+ involved);
+ long estimatedIterations = computeEstimatedIterations(perIterationBytes, bytesToMove);
+ long cycleTimeMillis = computeCycleTimeMillis(moveTimeoutMillis, balancingIntervalMillis);
+ long estimatedDurationMillis = estimatedIterations * cycleTimeMillis;
+
+ return builder
+ .setMaxDatanodesPercentage(maxDatanodesPercentage)
+ .setBytesToMove(bytesToMove)
+ .setPerIterationBytes(perIterationBytes)
+ .setEstimatedIterations(estimatedIterations)
+ .setEstimatedDurationMillis(estimatedDurationMillis)
+ .build();
+ } catch (IllegalArgumentException e) {
+ return builder
+ .setMaxDatanodesPercentage(maxDatanodesPercentage)
+ .setFailureMessage(e.getMessage())
+ .build();
+ }
+ }
+
+ /** Raises datanode involvement percent until at least two datanodes can be involved. */
+ static int minimumPercentForAtLeastTwoNodes(
+ int eligibleDatanodeCount, int startPercent, int maxPercent) {
+ for (int percent = startPercent; percent <= maxPercent; percent++) {
+ if (ContainerBalancerConfiguration.computeMaxDatanodesToInvolvePerIteration(
+ percent / 100d, eligibleDatanodeCount) >= 2) {
+ return percent;
+ }
+ }
+ return maxPercent;
+ }
+
+ /**
+ * Estimated bytes moved in one iteration: minimum of global cap, source cap,
+ * target cap, and total bytes to move.
+ */
+ static long computePerIterationBytes(
+ long bytesToMove,
+ long maxSizeToMovePerIteration,
+ long maxSizeLeavingSource,
+ long maxSizeEnteringTarget,
+ int[] involved) {
+
+ long fromLeaving = involved[0] * maxSizeLeavingSource;
+ long fromEntering = involved[1] * maxSizeEnteringTarget;
+
+ return minPositive(
+ maxSizeToMovePerIteration,
+ fromLeaving,
+ fromEntering,
+ bytesToMove);
+ }
+
+ static long computeCycleTimeMillis(long moveTimeoutMillis, long balancingIntervalMillis) {
+ return moveTimeoutMillis + balancingIntervalMillis;
+ }
+
+ static long computeEstimatedIterations(long perIterationBytes, long bytesToMove) {
+ if (perIterationBytes <= 0) {
+ throw new IllegalArgumentException("Per-iteration move size must be positive.");
+ }
+ return (long) Math.ceil(bytesToMove / (double) perIterationBytes);
+ }
+
+ /**
+ * Estimated source and target datanode counts for one iteration (50/50 split heuristic).
+ *
+ * @return {@code [involvedSources, involvedTargets]}
+ */
+ static int[] computeInvolvedDatanodeCounts(
+ int sourceCount,
+ int targetCount,
+ int maxInvolved) {
+ if (sourceCount <= 0 || targetCount <= 0 || maxInvolved < 2) {
+ return new int[] {0, 0};
+ }
+
+ int sEff = Math.min(sourceCount, (maxInvolved + 1) / 2);
+ int tEff = Math.min(targetCount, maxInvolved / 2);
+ return new int[] {sEff, tEff};
+ }
+
+ private static long minPositive(long... values) {
+ long result = Long.MAX_VALUE;
+ for (long value : values) {
+ if (value > 0 && value < result) {
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ private static void validateSnapshotForEstimation(ContainerBalancerClusterSnapshot snapshot,
+ OzoneConfiguration conf) {
+ long containerSizeBytes = (long) conf.getStorageSize(
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE,
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE_DEFAULT,
+ StorageUnit.BYTES);
+ if (snapshot.getSourceCount() < 1) {
+ throw new IllegalArgumentException("No over-utilized datanodes (sources) found.");
+ }
+ if (snapshot.getTargetCount() < 1) {
+ throw new IllegalArgumentException("No under-utilized datanodes (targets) found.");
+ }
+ if (snapshot.getBytesToMove() <= 0) {
+ throw new IllegalArgumentException("No bytes to move.");
+ }
+ if (snapshot.getBytesToMove() < containerSizeBytes) {
+ throw new IllegalArgumentException(
+ "Bytes to move (" + snapshot.getBytesToMove()
+ + ") is less than container size (" + containerSizeBytes + ").");
+ }
+ if (snapshot.getTotalEligibleDatanodes() < 2) {
+ throw new IllegalArgumentException(String.format(
+ "Container Balancer found %d eligible datanode(s) but requires at least 2.",
+ snapshot.getTotalEligibleDatanodes()));
+ }
+ }
+
+ private static void validateResolvedMoveLimits(OzoneConfiguration conf, long maxSizeEnteringTarget,
+ long maxSizeLeavingSource, long maxSizeToMovePerIteration) {
+ long containerSizeBytes = (long) conf.getStorageSize(
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE,
+ ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE_DEFAULT,
+ StorageUnit.BYTES);
+
+ if (maxSizeEnteringTarget <= 0) {
+ throw new IllegalArgumentException("Max Size Entering Target must be greater than zero.");
+ }
+ if (maxSizeEnteringTarget <= containerSizeBytes) {
+ throw new IllegalArgumentException(
+ "max-size-entering-target must be greater than ozone.scm.container.size ("
+ + containerSizeBytes + " bytes).");
+ }
+ if (maxSizeLeavingSource <= 0) {
+ throw new IllegalArgumentException("Max Size Leaving Source must be greater than zero.");
+ }
Review Comment:
Merge multiple condition into single to improve redability.
##########
hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerConfiguration.java:
##########
@@ -467,6 +526,48 @@ public void setIncludeNonStandardContainers(boolean enable) {
includeNonStandardContainers = enable;
}
+ /** Returns the preset max datanode involvement percent for the given profile. */
+ public int getProfileDatanodesMaxPercentage(ContainerBalancerProfile profile) {
+ switch (profile) {
+ case SLOW:
+ return profileSlowDatanodesMaxPercentage;
+ case MEDIUM:
+ return profileMediumDatanodesMaxPercentage;
+ case FAST:
+ return profileFastDatanodesMaxPercentage;
+ default:
+ throw new IllegalArgumentException("Unknown profile: " + profile);
+ }
+ }
+
+ /** Returns the preset max bytes entering a target datanode per iteration for the given profile. */
+ public long getProfileMaxSizeEnteringTarget(ContainerBalancerProfile profile) {
+ switch (profile) {
+ case SLOW:
+ return profileSlowMaxSizeEnteringTarget;
+ case MEDIUM:
+ return profileMediumMaxSizeEnteringTarget;
+ case FAST:
+ return profileFastMaxSizeEnteringTarget;
+ default:
+ throw new IllegalArgumentException("Unknown profile: " + profile);
+ }
+ }
+
+ /** Returns the preset max bytes leaving a source datanode per iteration for the given profile. */
+ public long getProfileMaxSizeLeavingSource(ContainerBalancerProfile profile) {
Review Comment:
Create ContainerBalancerProfile enum like below and remove multiple methods here.
```
public enum ContainerBalancerProfile {
SLOW(10, 10L, 10L),
MEDIUM(20, 26L, 26L),
FAST(40, 100L, 100L);
```
....
}
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Dry run command for container balancer with profiles [ozone]
Posted by "sravani-revuri (via GitHub)" <gi...@apache.org>.
sravani-revuri commented on code in PR #11255:
URL: https://github.com/apache/ozone/pull/11255#discussion_r4070848523
##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerBalancerDryRunSubcommand.java:
##########
@@ -0,0 +1,166 @@
+/*
+ * 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.hadoop.hdds.scm.cli;
+
+import static org.apache.hadoop.util.StringUtils.byteDesc;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerAdvisor;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerEstimation;
+import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerProfile;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Estimates container balancer bytes to move, per iteration bytes, estimated iterations, estimated duration
+ * without starting the balancer.
+ */
+@Command(
+ name = "dry-run",
+ description = "Estimate container balancer bytes to move, iterations, per iteration bytes " +
+ "and upper-bound duration without starting it. Balancer limits and profile presets are read from " +
+ "the local Ozone configuration (including ozone-site.xml), datanode usage is fetched from SCM.",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class ContainerBalancerDryRunSubcommand extends ScmSubcommand {
+
+ private static final double PLANNING_ITERATION_BUFFER = 1.3d;
+
+ @CommandLine.Mixin
+ private ContainerBalancerConfigOptions configOptions;
+
+ @CommandLine.ArgGroup(exclusive = true, multiplicity = "0..1")
+ private ProfileSelection profileSelection;
+
+ @Override
+ public void execute(ScmClient scmClient) throws IOException {
+
+ List<DatanodeUsageInfoProto> nodes = scmClient.getDatanodeUsageInfo(true, Integer.MAX_VALUE);
+ if (nodes == null || nodes.isEmpty()) {
+ throw new IOException("No datanode usage information available from SCM.");
+ }
+
+ OzoneConfiguration conf = getOzoneConf();
+ ContainerBalancerAdvisor.AdvisorRequest request = buildRequest(nodes);
+ List<ContainerBalancerEstimation> estimations;
+ try {
+ estimations = ContainerBalancerAdvisor.estimateDryRun(conf, request);
+ } catch (IllegalArgumentException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+
+ boolean anySucceeded = false;
+ for (ContainerBalancerEstimation result : estimations) {
Review Comment:
when no profile is specified would it be better to specify that we're going with default?
something like :
```
No profile specified, using the default.
Profile: MEDIUM
...
```
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Estimate subcommand for container balancer with profiles [ozone]
Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai commented on code in PR #11255:
URL: https://github.com/apache/ozone/pull/11255#discussion_r4102435364
##########
hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancerAdvisor.java:
##########
@@ -0,0 +1,296 @@
+/*
+ * 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.hadoop.hdds.scm.container.balancer;
+
+import static org.apache.hadoop.ozone.ClientVersion.DEFAULT_VERSION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeUsageInfoProto;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link ContainerBalancerAdvisor} estimation. */
+public final class TestContainerBalancerAdvisor {
+
+ @Test
+ void testComputePerIterationBytesNeverExceedsBytesToMove() {
+ int[] involved = {3, 3};
+ long bytesToMove = 50L * OzoneConsts.GB;
+
+ assertEquals(bytesToMove, ContainerBalancerAdvisor.computePerIterationBytes(
+ bytesToMove,
+ 500L * OzoneConsts.GB,
+ 26 * OzoneConsts.GB,
+ 26 * OzoneConsts.GB,
+ involved));
+ }
+
+ @Test
+ void testEstimateDefaultReturnsMediumProfile() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ List<ContainerBalancerEstimation> results = ContainerBalancerAdvisor.estimate(
+ conf,
+ new ContainerBalancerAdvisor.AdvisorRequest().setNodes(buildCluster(70, 14, 14)));
+
+ assertEquals(1, results.size());
+ ContainerBalancerEstimation estimation = results.get(0);
+ assertThat(estimation.succeeded()).isTrue();
Review Comment:
For future patches: `assertTrue/assertFalse` is OK for the "boolean assertion" case (when `assertThat` does not provide richer assertions, only `isTrue`/`isFalse`). But no need to change it back now.
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org
Re: [PR] HDDS-16179. Estimate subcommand for container balancer with profiles [ozone]
Posted by "ashishkumar50 (via GitHub)" <gi...@apache.org>.
ashishkumar50 merged PR #11255:
URL: https://github.com/apache/ozone/pull/11255
--
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.
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org