Skip to content

Commit

Permalink
Autoscaling default deciders (elastic#65558)
Browse files Browse the repository at this point in the history
Autoscaling policies now have default deciders depending on which roles
the policy governs.
  • Loading branch information
henningandersen committed Dec 9, 2020
1 parent bcdaafe commit 4540958
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.elasticsearch.xpack.autoscaling.capacity.AutoscalingDeciderService.EMPTY_ROLES;

public class AutoscalingCalculateCapacityService implements PolicyValidator {
private final Map<String, AutoscalingDeciderService> deciderByName;

Expand All @@ -43,18 +45,22 @@ public AutoscalingCalculateCapacityService(Set<AutoscalingDeciderService> decide
}

public void validate(AutoscalingPolicy policy) {
policy.deciders().forEach(this::validate);
policy.deciders().forEach((name, configuration) -> validate(name, configuration, policy.roles()));
}

private void validate(final String deciderName, final Settings configuration) {
private void validate(final String deciderName, final Settings configuration, SortedSet<String> roles) {
AutoscalingDeciderService deciderService = deciderByName.get(deciderName);
if (deciderService == null) {
throw new IllegalArgumentException("unknown decider [" + deciderName + "]");
}

if (appliesToPolicy(deciderService, roles) == false) {
throw new IllegalArgumentException("decider [" + deciderName + "] not applicable to policy with roles [ " + roles + "]");
}

Map<String, Setting<?>> deciderSettings = deciderService.deciderSettings()
.stream()
.collect(Collectors.toMap(s -> s.getKey(), Function.identity()));
.collect(Collectors.toMap(Setting::getKey, Function.identity()));

configuration.keySet().forEach(key -> validateSetting(key, configuration, deciderSettings, deciderName));
}
Expand Down Expand Up @@ -112,15 +118,40 @@ private AutoscalingDeciderResults calculateForPolicy(AutoscalingPolicy policy, C
new TreeMap<>(org.elasticsearch.common.collect.Map.of("_unknown_role", new AutoscalingDeciderResult(null, null)))
);
}
SortedMap<String, Settings> deciders = addDefaultDeciders(policy);
DefaultAutoscalingDeciderContext context = new DefaultAutoscalingDeciderContext(policy.roles(), state, clusterInfo);
SortedMap<String, AutoscalingDeciderResult> results = policy.deciders()
.entrySet()
SortedMap<String, AutoscalingDeciderResult> results = deciders.entrySet()
.stream()
.map(entry -> Tuple.tuple(entry.getKey(), calculateForDecider(entry.getKey(), entry.getValue(), context)))
.collect(Collectors.toMap(Tuple::v1, Tuple::v2, (a, b) -> { throw new UnsupportedOperationException(); }, TreeMap::new));
return new AutoscalingDeciderResults(context.currentCapacity, context.currentNodes, results);
}

private SortedMap<String, Settings> addDefaultDeciders(AutoscalingPolicy policy) {
SortedMap<String, Settings> deciders = new TreeMap<>(policy.deciders());
deciderByName.entrySet()
.stream()
.filter(e -> defaultForPolicy(e.getValue(), policy.roles()))
.forEach(e -> deciders.putIfAbsent(e.getKey(), Settings.EMPTY));
return deciders;
}

private boolean defaultForPolicy(AutoscalingDeciderService deciderService, SortedSet<String> roles) {
if (deciderService.defaultOn()) {
return appliesToPolicy(deciderService, roles);
} else {
return false;
}
}

private boolean appliesToPolicy(AutoscalingDeciderService deciderService, SortedSet<String> roles) {
if (roles.isEmpty()) {
return deciderService.roles().contains(EMPTY_ROLES);
} else {
return deciderService.roles().stream().map(DiscoveryNodeRole::roleName).anyMatch(roles::contains);
}
}

/**
* Check if the policy has unknown roles. This can only happen in mixed clusters, where one master can accept a policy but if it fails
* over to an older master before it is also upgraded, one of the roles might not be known.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package org.elasticsearch.xpack.autoscaling.capacity;

import org.elasticsearch.cluster.node.DiscoveryNodeRole;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;

Expand All @@ -16,6 +17,16 @@
*/
public interface AutoscalingDeciderService {

/**
* A marker role to use to also match policies having an empty set of roles.
*/
DiscoveryNodeRole EMPTY_ROLES = new DiscoveryNodeRole("_empty", "_empty") {
@Override
public Setting<Boolean> legacySetting() {
return null;
}
};

/**
* The name of the autoscaling decider.
*
Expand All @@ -33,4 +44,20 @@ public interface AutoscalingDeciderService {
AutoscalingDeciderResult scale(Settings configuration, AutoscalingDeciderContext context);

List<Setting<?>> deciderSettings();

/**
* The roles that this decider applies to. The decider will automatically be applied to policies that has any of the roles returned,
* using the default values for settings if not overridden on the policy.
*
* Returning the empty list signals a special case of a decider that require explicit configuration to be enabled for a policy and
* has no restrictions on the roles it applies to. This is intended only for supplying deciders useful for testing.
*/
List<DiscoveryNodeRole> roles();

/**
* Is the decider default on for policies matching the roles() of this decider service?
*/
default boolean defaultOn() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package org.elasticsearch.xpack.autoscaling.capacity;

import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
Expand All @@ -15,6 +17,8 @@
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

Expand All @@ -25,9 +29,14 @@ public class FixedAutoscalingDeciderService implements AutoscalingDeciderService
public static final Setting<ByteSizeValue> STORAGE = Setting.byteSizeSetting("storage", ByteSizeValue.ofBytes(-1));
public static final Setting<ByteSizeValue> MEMORY = Setting.byteSizeSetting("memory", ByteSizeValue.ofBytes(-1));
public static final Setting<Integer> NODES = Setting.intSetting("nodes", 1, 0);
private final List<DiscoveryNodeRole> appliesToRoles;

@Inject
public FixedAutoscalingDeciderService() {}
public FixedAutoscalingDeciderService() {
ArrayList<DiscoveryNodeRole> appliesToRoles = new ArrayList<>(DiscoveryNode.getPossibleRoles());
appliesToRoles.add(EMPTY_ROLES);
this.appliesToRoles = Collections.unmodifiableList(appliesToRoles);
}

@Override
public String name() {
Expand Down Expand Up @@ -65,6 +74,16 @@ public List<Setting<?>> deciderSettings() {
return org.elasticsearch.common.collect.List.of(STORAGE, MEMORY, NODES);
}

@Override
public List<DiscoveryNodeRole> roles() {
return appliesToRoles;
}

@Override
public boolean defaultOn() {
return false;
}

public static class FixedReason implements AutoscalingDeciderResult.Reason {

private final ByteSizeValue storage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
Expand Down Expand Up @@ -84,6 +86,47 @@ public void testMultiplePoliciesFixedCapacity() {
}
}

public void testDefaultDeciders() {
FixedAutoscalingDeciderService defaultOn = new FixedAutoscalingDeciderService() {
@Override
public boolean defaultOn() {
return true;
}

@Override
public String name() {
return "default_on";
}
};

FixedAutoscalingDeciderService defaultOff = new FixedAutoscalingDeciderService();

AutoscalingCalculateCapacityService service = new AutoscalingCalculateCapacityService(
org.elasticsearch.common.collect.Set.of(defaultOn, defaultOff)
);
ClusterState state = ClusterState.builder(ClusterName.DEFAULT)
.metadata(
Metadata.builder()
.putCustom(
AutoscalingMetadata.NAME,
new AutoscalingMetadata(
new TreeMap<>(
org.elasticsearch.common.collect.Map.of(
"test",
new AutoscalingPolicyMetadata(new AutoscalingPolicy("test", randomRoles(), new TreeMap<>()))
)
)
)
)
)
.build();

assertThat(
service.calculate(state, ClusterInfo.EMPTY).get("test").results().keySet(),
equalTo(org.elasticsearch.common.collect.Set.of(defaultOn.name()))
);
}

private SortedMap<String, Settings> randomFixedDeciders() {
Settings.Builder settings = Settings.builder();
if (randomBoolean()) {
Expand Down Expand Up @@ -205,6 +248,30 @@ public void testValidateDeciderName() {
assertThat(exception.getMessage(), equalTo("unknown decider [" + badDeciderName + "]"));
}

public void testValidateDeciderRoles() {
Set<String> roles = randomRoles();
AutoscalingCalculateCapacityService service = new AutoscalingCalculateCapacityService(
org.elasticsearch.common.collect.Set.of(new FixedAutoscalingDeciderService() {
@Override
public List<DiscoveryNodeRole> roles() {
return roles.stream().map(DiscoveryNode::getRoleFromRoleName).collect(Collectors.toList());
}
})
);
SortedSet<String> badRoles = new TreeSet<>(randomRoles());
badRoles.removeAll(roles);
AutoscalingPolicy policy = new AutoscalingPolicy(
FixedAutoscalingDeciderService.NAME,
badRoles,
new TreeMap<>(org.elasticsearch.common.collect.Map.of(FixedAutoscalingDeciderService.NAME, Settings.EMPTY))
);
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> service.validate(policy));
assertThat(
exception.getMessage(),
equalTo("decider [" + FixedAutoscalingDeciderService.NAME + "] not applicable to policy with roles [ " + badRoles + "]")
);
}

public void testValidateSettingName() {
AutoscalingCalculateCapacityService service = new AutoscalingCalculateCapacityService(
org.elasticsearch.common.collect.Set.of(new FixedAutoscalingDeciderService())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.LocalNodeMasterListener;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.LifecycleListener;
Expand Down Expand Up @@ -605,5 +606,10 @@ public String name() {
public List<Setting<?>> deciderSettings() {
return org.elasticsearch.common.collect.List.of(NUM_ANALYTICS_JOBS_IN_QUEUE, NUM_ANOMALY_JOBS_IN_QUEUE, DOWN_SCALE_DELAY);
}

@Override
public List<DiscoveryNodeRole> roles() {
return org.elasticsearch.common.collect.List.of(MachineLearning.ML_ROLE);
}
}

0 comments on commit 4540958

Please sign in to comment.