Skip to content

server: reserve cluster capacity for HA failover (HA admission control) - #14042

Open
nagaboinaramgopal wants to merge 2 commits into
apache:mainfrom
nagaboinaramgopal:feature/ha-failover-capacity-reserve
Open

nagaboinaramgopal wants to merge 2 commits into
apache:mainfrom
nagaboinaramgopal:feature/ha-failover-capacity-reserve

Conversation

@nagaboinaramgopal

Copy link
Copy Markdown
Contributor

Description

Adds optional HA admission control: an operator can reserve a fraction of each cluster's CPU/memory so new deployments leave headroom for HA-triggered restarts when a host fails.

A new cluster-scoped setting cluster.ha.failover.capacity.reservethreshold (Float, default 1.0 = disabled) controls it. Below 1.0, FirstFitPlanner excludes any cluster whose allocated + requested CPU or memory would cross that fraction from new deployments, using the same listClustersCrossingThreshold path the capacity disable threshold already uses. HA restarts are not subject to the reserve, so the headroom is available exactly when failover needs it. Off by default, so existing behaviour is unchanged until an operator opts in per cluster.

For example, 0.8 keeps about 20% of a cluster's CPU and memory free for failover. It is meant to be set below the corresponding cluster.*.allocated.capacity.disablethreshold.

Types of changes

  • New feature (non-breaking change which adds functionality)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Minor

How Has This Been Tested?

Added unit tests in FirstFitPlannerTest:

  • checkHAFailoverReserveDisabledByDefault: at the default 1.0 the cluster selection is unchanged.
  • checkHAFailoverReserveExcludesClusterOnDeploy: a cluster whose allocation would cross the reserve is excluded from a new deployment.

Also built the standard packages and deployed on a KVM advanced zone.

Adds cluster.ha.failover.capacity.reservethreshold (default 1.0 =
disabled). When set below 1.0, FirstFitPlanner also excludes any cluster
whose allocated+requested cpu/memory would cross the reserve from new
deployments, so headroom stays free for HA-triggered restarts. The
threshold is per-cluster scope aware.
@nagaboinaramgopal

Copy link
Copy Markdown
Contributor Author

@DaanHoogland Could you pls take a look at this

@DaanHoogland

Copy link
Copy Markdown
Contributor

@NuxRo @ingox , could you have a look at the functionality?

ConfigKey.Scope.Global);
// Reserve spare cluster capacity for HA failover.
static final ConfigKey<Float> ClusterHAFailoverReserveThreshold =
new ConfigKey<Float>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new ConfigKey<Float>(
new ConfigKey<>(

Comment on lines +391 to +403
// Exclude clusters that would cross the HA failover reserve threshold, reserving
// capacity for HA-triggered restarts. Threshold is Cluster-scoped, resolved per-cluster by the DAO.
long haRequested = (capacity == Capacity.CAPACITY_TYPE_CPU) ? cpu_requested : ram_requested;
List<Long> clustersCrossingHAReserve = capacityDao.listClustersCrossingThreshold(
capacity, plan.getDataCenterId(), ClusterHAFailoverReserveThreshold.key(), haRequested);
if (clustersCrossingHAReserve != null && !clustersCrossingHAReserve.isEmpty()) {
avoid.addClusterList(clustersCrossingHAReserve);
clusterListForVmAllocation.removeAll(clustersCrossingHAReserve);
logger.warn(String.format(
"HA admission control: excluding clusters %s from new deployments; their %s allocation would cross the HA failover reserve threshold [%s], reserving capacity for HA failover",
clustersCrossingHAReserve, CapacityVO.getCapacityName(capacity),
ClusterHAFailoverReserveThreshold.key()));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please extract a method and use the comment as javadoc

@DaanHoogland DaanHoogland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code looks good

@codecov

codecov Bot commented Sep 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 3.53%. Comparing base (3d70ce4) to head (6174c77).
⚠️ Report is 73 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff           @@
##            main   #14042   +/-   ##
======================================
  Coverage   3.53%    3.53%           
======================================
  Files        487      487           
  Lines      41863    41863           
  Branches    7912     7912           
======================================
  Hits        1479     1479           
  Misses     40170    40170           
  Partials     214      214           
Flag Coverage Δ
uitests 3.53% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Use the diamond operator for the ClusterHAFailoverReserveThreshold ConfigKey,
and extract the HA reserve exclusion into excludeClustersCrossingHAReserve with
the former inline comment as its javadoc.
@nagaboinaramgopal

Copy link
Copy Markdown
Contributor Author

Thanks @DaanHoogland. Done in 41c7210: used the diamond for the ConfigKey, and pulled that block into its own method (excludeClustersCrossingHAReserve) with the comment as the javadoc.

logger.warn(warnMessageForClusterReachedCapacityThreshold);
}

excludeClustersCrossingHAReserve(capacity, cpu_requested, ram_requested, plan, avoid, clusterListForVmAllocation);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this also block ha restarts? cluster.threshold.enabled is on by default, so this check runs on every start, not just new deploys

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. The reserve check lives inside removeClustersCrossingThreshold, so it does run on the first HA attempt, since HA starts the VM with its original planner. But if the VM can't fit outside the reserve, HA falls back to SkipHeuresticsPlanner, which overrides removeClustersCrossingThreshold to a no-op ("Deploying vm during HA process, so skipping disable threshold check"). That skips the reserve exclusion too, so the restart lands in the reserved capacity. So the reserve keeps normal deployments out but stays available to HA restarts through that fallback.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants