// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.config.model.producer.AnyConfigProducer; import com.yahoo.config.model.producer.TreeConfigProducer; import com.yahoo.vespa.config.content.FleetcontrollerConfig; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.utils.Duration; import org.w3c.dom.Element; import java.util.Optional; /** * Config generation for parameters for fleet controllers. */ public class ClusterControllerConfig extends AnyConfigProducer implements FleetcontrollerConfig.Producer { public static class Builder extends VespaDomBuilder.DomConfigProducerBuilderBase { private final String clusterName; private final ModelElement clusterElement; private final ResourceLimits resourceLimits; public Builder(String clusterName, ModelElement clusterElement, ResourceLimits resourceLimits) { this.clusterName = clusterName; this.clusterElement = clusterElement; this.resourceLimits = resourceLimits; } @Override protected ClusterControllerConfig doBuild(DeployState deployState, TreeConfigProducer ancestor, Element producerSpec) { ModelElement tuning = clusterElement.child("tuning"); ModelElement clusterControllerTuning = null; Optional minNodeRatioPerGroup = Optional.of(deployState.featureFlags().minNodeRatioPerGroup()); Optional bucketSplittingMinimumBits = Optional.empty(); if (tuning != null) { minNodeRatioPerGroup = Optional.ofNullable(tuning.childAsDouble("min-node-ratio-per-group")); bucketSplittingMinimumBits = Optional.ofNullable(tuning.childAsInteger("bucket-splitting.minimum-bits")); clusterControllerTuning = tuning.child("cluster-controller"); } var numberOfLeafGroups = ((ContentCluster) ancestor).getRootGroup().getNumberOfLeafGroups(); var tuningConfig = new ClusterControllerTuningBuilder(clusterControllerTuning, minNodeRatioPerGroup, bucketSplittingMinimumBits, numberOfLeafGroups) .build(); return new ClusterControllerConfig(ancestor, clusterName, tuningConfig, resourceLimits); } } private final String clusterName; private final ClusterControllerTuning tuning; private final ResourceLimits resourceLimits; private ClusterControllerConfig(TreeConfigProducer parent, String clusterName, ClusterControllerTuning tuning, ResourceLimits resourceLimits) { super(parent, "fleetcontroller"); this.clusterName = clusterName; this.tuning = tuning; this.resourceLimits = resourceLimits; } @Override public void getConfig(FleetcontrollerConfig.Builder builder) { AbstractConfigProducerRoot root = getRoot(); if (root instanceof VespaModel) { String zooKeeperAddress = root.getAdmin().getZooKeepersConfigProvider().getZooKeepersConnectionSpec(); builder.zookeeper_server(zooKeeperAddress); } else { builder.zookeeper_server(""); } builder.index(0); builder.cluster_name(clusterName); builder.fleet_controller_count(getChildren().size()); tuning.initProgressTime.ifPresent(i -> builder.init_progress_time((int) i.getMilliSeconds())); tuning.transitionTime.ifPresent(t -> builder.storage_transition_time((int) t.getMilliSeconds())); tuning.maxPrematureCrashes.ifPresent(var -> builder.max_premature_crashes(var.intValue())); tuning.stableStateTimePeriod.ifPresent(var -> builder.stable_state_time_period((int) var.getMilliSeconds())); tuning.minDistributorUpRatio.ifPresent(builder::min_distributor_up_ratio); tuning.minStorageUpRatio.ifPresent(builder::min_storage_up_ratio); tuning.minSplitBits.ifPresent(builder::ideal_distribution_bits); tuning.minNodeRatioPerGroup.ifPresent(builder::min_node_ratio_per_group); tuning.maxGroupsAllowedDown.ifPresent(builder::max_number_of_groups_allowed_to_be_down); resourceLimits.getConfig(builder); } public ClusterControllerTuning tuning() {return tuning;} private static class ClusterControllerTuningBuilder { private final Optional minNodeRatioPerGroup; private final Optional initProgressTime; private final Optional transitionTime; private final Optional maxPrematureCrashes; private final Optional stableStateTimePeriod; private final Optional minDistributorUpRatio; private final Optional minStorageUpRatio; private final Optional minSplitBits; private final Optional maxGroupsAllowedDown; ClusterControllerTuningBuilder(ModelElement tuning, Optional minNodeRatioPerGroup, Optional bucketSplittingMinimumBits, int numberOfLeafGroups) { this.minSplitBits = bucketSplittingMinimumBits; this.minNodeRatioPerGroup = minNodeRatioPerGroup; if (tuning == null) { this.initProgressTime = Optional.empty(); this.transitionTime = Optional.empty(); this.maxPrematureCrashes = Optional.empty(); this.stableStateTimePeriod = Optional.empty(); this.minDistributorUpRatio = Optional.empty(); this.minStorageUpRatio = Optional.empty(); this.maxGroupsAllowedDown = Optional.empty(); } else { this.initProgressTime = Optional.ofNullable(tuning.childAsDuration("init-progress-time")); this.transitionTime = Optional.ofNullable(tuning.childAsDuration("transition-time")); this.maxPrematureCrashes = Optional.ofNullable(tuning.childAsLong("max-premature-crashes")); this.stableStateTimePeriod = Optional.ofNullable(tuning.childAsDuration("stable-state-period")); this.minDistributorUpRatio = Optional.ofNullable(tuning.childAsDouble("min-distributor-up-ratio")); this.minStorageUpRatio = Optional.ofNullable(tuning.childAsDouble("min-storage-up-ratio")); this.maxGroupsAllowedDown = maxGroupsAllowedDown(tuning, numberOfLeafGroups); } } private static Optional maxGroupsAllowedDown(ModelElement tuning, int numberOfLeafGroups) { var groupsAllowedDownRatio = tuning.childAsDouble("groups-allowed-down-ratio"); if (groupsAllowedDownRatio != null) { if (groupsAllowedDownRatio < 0 || groupsAllowedDownRatio > 1) throw new IllegalArgumentException("groups-allowed-down-ratio must be between 0 and 1, got " + groupsAllowedDownRatio); var maxGroupsAllowedDown = Math.max(1, (int) Math.floor(groupsAllowedDownRatio * numberOfLeafGroups)); return Optional.of(maxGroupsAllowedDown); } return Optional.empty(); } private ClusterControllerTuning build() { return new ClusterControllerTuning(initProgressTime, transitionTime, maxPrematureCrashes, stableStateTimePeriod, minDistributorUpRatio, minStorageUpRatio, maxGroupsAllowedDown, minNodeRatioPerGroup, minSplitBits); } } private record ClusterControllerTuning(Optional initProgressTime, Optional transitionTime, Optional maxPrematureCrashes, Optional stableStateTimePeriod, Optional minDistributorUpRatio, Optional minStorageUpRatio, Optional maxGroupsAllowedDown, Optional minNodeRatioPerGroup, Optional minSplitBits) { } }