Autoscaling decider and decision service (#59005) (#60884)

Split the autoscaling decider into a service and configuration
in order to enable having additional context information available
in the service. Added AutoscalingDeciderContext holding generic
information all deciders are expected to need. Implemented GET
_autoscaling/decision
This commit is contained in:
Henning Andersen 2020-08-10 15:28:52 +02:00 committed by GitHub
parent 8a0f1d8746
commit a155315ceb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 458 additions and 51 deletions

View File

@ -25,6 +25,17 @@ PUT /_autoscaling/policy/my_autoscaling_policy
--------------------------------------------------
// TESTSETUP
//////////////////////////
[source,console]
--------------------------------------------------
DELETE /_autoscaling/policy/my_autoscaling_policy
--------------------------------------------------
// TEST
// TEARDOWN
//////////////////////////
[source,console]
--------------------------------------------------
GET /_autoscaling/policy/<name>

View File

@ -25,6 +25,16 @@ PUT /_autoscaling/policy/<name>
--------------------------------------------------
// TEST[s/<name>/name/]
//////////////////////////
[source,console]
--------------------------------------------------
DELETE /_autoscaling/policy/name
--------------------------------------------------
// TEST[continued]
//////////////////////////
[[autoscaling-put-autoscaling-policy-prereqs]]
==== {api-prereq-title}
@ -65,3 +75,13 @@ The API returns the following result:
"acknowledged": true
}
--------------------------------------------------
//////////////////////////
[source,console]
--------------------------------------------------
DELETE /_autoscaling/policy/my_autoscaling_policy
--------------------------------------------------
// TEST[continued]
//////////////////////////

View File

@ -1,6 +1,31 @@
---
"Test get autoscaling decision":
"Test get empty autoscaling decision":
- do:
autoscaling.get_autoscaling_decision: {}
- match: { "decisions": [] }
---
"Test get always autoscaling decision":
- do:
autoscaling.put_autoscaling_policy:
name: my_autoscaling_policy
body:
policy:
deciders:
always: {}
- match: { "acknowledged": true }
- do:
autoscaling.get_autoscaling_decision: {}
- match: { decisions.0.my_autoscaling_policy.decision: scale_up }
- match: { decisions.0.my_autoscaling_policy.decisions.0.name: always }
- match: { decisions.0.my_autoscaling_policy.decisions.0.type: scale_up }
- match: { decisions.0.my_autoscaling_policy.decisions.0.reason: always }
# test cleanup
- do:
autoscaling.delete_autoscaling_policy:
name: my_autoscaling_policy

View File

@ -10,6 +10,11 @@
- match: { "acknowledged": true }
# test cleanup
- do:
autoscaling.delete_autoscaling_policy:
name: my_autoscaling_policy
---
"Test put autoscaling policy with non-existent decider":
- do:

View File

@ -6,13 +6,17 @@
package org.elasticsearch.xpack.autoscaling;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Build;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.ClusterSettings;
@ -21,11 +25,18 @@ import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.ExtensiblePlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.repositories.RepositoriesService;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.autoscaling.action.DeleteAutoscalingPolicyAction;
import org.elasticsearch.xpack.autoscaling.action.GetAutoscalingDecisionAction;
import org.elasticsearch.xpack.autoscaling.action.GetAutoscalingPolicyAction;
@ -34,22 +45,29 @@ import org.elasticsearch.xpack.autoscaling.action.TransportDeleteAutoscalingPoli
import org.elasticsearch.xpack.autoscaling.action.TransportGetAutoscalingDecisionAction;
import org.elasticsearch.xpack.autoscaling.action.TransportGetAutoscalingPolicyAction;
import org.elasticsearch.xpack.autoscaling.action.TransportPutAutoscalingPolicyAction;
import org.elasticsearch.xpack.autoscaling.decision.AlwaysAutoscalingDecider;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecider;
import org.elasticsearch.xpack.autoscaling.decision.AlwaysAutoscalingDeciderConfiguration;
import org.elasticsearch.xpack.autoscaling.decision.AlwaysAutoscalingDeciderService;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDeciderConfiguration;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDeciderService;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisionService;
import org.elasticsearch.xpack.autoscaling.rest.RestDeleteAutoscalingPolicyHandler;
import org.elasticsearch.xpack.autoscaling.rest.RestGetAutoscalingDecisionHandler;
import org.elasticsearch.xpack.autoscaling.rest.RestGetAutoscalingPolicyHandler;
import org.elasticsearch.xpack.autoscaling.rest.RestPutAutoscalingPolicyHandler;
import org.elasticsearch.xpack.core.XPackPlugin;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Container class for autoscaling functionality.
*/
public class Autoscaling extends Plugin implements ActionPlugin {
public class Autoscaling extends Plugin implements ActionPlugin, ExtensiblePlugin, AutoscalingExtension {
private static final Logger logger = LogManager.getLogger(AutoscalingExtension.class);
private static final Boolean AUTOSCALING_FEATURE_FLAG_REGISTERED;
static {
@ -78,8 +96,11 @@ public class Autoscaling extends Plugin implements ActionPlugin {
private final boolean enabled;
private final List<AutoscalingExtension> autoscalingExtensions;
public Autoscaling(final Settings settings) {
this.enabled = AUTOSCALING_ENABLED_SETTING.get(settings);
this.autoscalingExtensions = new ArrayList<>(org.elasticsearch.common.collect.List.of(this));
}
/**
@ -100,6 +121,23 @@ public class Autoscaling extends Plugin implements ActionPlugin {
return Build.CURRENT.isSnapshot();
}
@Override
public Collection<Object> createComponents(
Client client,
ClusterService clusterService,
ThreadPool threadPool,
ResourceWatcherService resourceWatcherService,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
Environment environment,
NodeEnvironment nodeEnvironment,
NamedWriteableRegistry namedWriteableRegistry,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<RepositoriesService> repositoriesServiceSupplier
) {
return org.elasticsearch.common.collect.List.of(new AutoscalingDecisionService.Holder(this));
}
@Override
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
if (enabled) {
@ -141,7 +179,11 @@ public class Autoscaling extends Plugin implements ActionPlugin {
return org.elasticsearch.common.collect.List.of(
new NamedWriteableRegistry.Entry(Metadata.Custom.class, AutoscalingMetadata.NAME, AutoscalingMetadata::new),
new NamedWriteableRegistry.Entry(NamedDiff.class, AutoscalingMetadata.NAME, AutoscalingMetadata.AutoscalingMetadataDiff::new),
new NamedWriteableRegistry.Entry(AutoscalingDecider.class, AlwaysAutoscalingDecider.NAME, AlwaysAutoscalingDecider::new)
new NamedWriteableRegistry.Entry(
AutoscalingDeciderConfiguration.class,
AlwaysAutoscalingDeciderConfiguration.NAME,
AlwaysAutoscalingDeciderConfiguration::new
)
);
}
@ -150,9 +192,9 @@ public class Autoscaling extends Plugin implements ActionPlugin {
return org.elasticsearch.common.collect.List.of(
new NamedXContentRegistry.Entry(Metadata.Custom.class, new ParseField(AutoscalingMetadata.NAME), AutoscalingMetadata::parse),
new NamedXContentRegistry.Entry(
AutoscalingDecider.class,
new ParseField(AlwaysAutoscalingDecider.NAME),
AlwaysAutoscalingDecider::parse
AutoscalingDeciderConfiguration.class,
new ParseField(AlwaysAutoscalingDeciderConfiguration.NAME),
AlwaysAutoscalingDeciderConfiguration::parse
)
);
}
@ -161,4 +203,17 @@ public class Autoscaling extends Plugin implements ActionPlugin {
return XPackPlugin.getSharedLicenseState();
}
@Override
public void loadExtensions(ExtensionLoader loader) {
loader.loadExtensions(AutoscalingExtension.class).forEach(autoscalingExtensions::add);
}
@Override
public Collection<AutoscalingDeciderService<? extends AutoscalingDeciderConfiguration>> deciders() {
return org.elasticsearch.common.collect.List.of(new AlwaysAutoscalingDeciderService());
}
public Set<AutoscalingDeciderService<? extends AutoscalingDeciderConfiguration>> createDeciderServices() {
return autoscalingExtensions.stream().flatMap(p -> p.deciders().stream()).collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDeciderConfiguration;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDeciderService;
import java.util.Collection;
public interface AutoscalingExtension {
/**
* Get the list of decider services for this plugin. This is called after createComponents has been called.
* @return list of decider services
*/
Collection<AutoscalingDeciderService<? extends AutoscalingDeciderConfiguration>> deciders();
}

View File

@ -17,22 +17,24 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisionService;
import java.io.IOException;
import java.util.Collections;
import java.util.TreeMap;
public class TransportGetAutoscalingDecisionAction extends TransportMasterNodeAction<
GetAutoscalingDecisionAction.Request,
GetAutoscalingDecisionAction.Response> {
private final AutoscalingDecisionService decisionService;
@Inject
public TransportGetAutoscalingDecisionAction(
final TransportService transportService,
final ClusterService clusterService,
final ThreadPool threadPool,
final ActionFilters actionFilters,
final IndexNameExpressionResolver indexNameExpressionResolver
final IndexNameExpressionResolver indexNameExpressionResolver,
final AutoscalingDecisionService.Holder decisionServiceHolder
) {
super(
GetAutoscalingDecisionAction.NAME,
@ -43,6 +45,8 @@ public class TransportGetAutoscalingDecisionAction extends TransportMasterNodeAc
GetAutoscalingDecisionAction.Request::new,
indexNameExpressionResolver
);
this.decisionService = decisionServiceHolder.get();
assert this.decisionService != null;
}
@Override
@ -61,7 +65,7 @@ public class TransportGetAutoscalingDecisionAction extends TransportMasterNodeAc
final ClusterState state,
final ActionListener<GetAutoscalingDecisionAction.Response> listener
) {
listener.onResponse(new GetAutoscalingDecisionAction.Response(Collections.unmodifiableSortedMap(new TreeMap<>())));
listener.onResponse(new GetAutoscalingDecisionAction.Response(decisionService.decide(state)));
}
@Override

View File

@ -14,20 +14,23 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
public class AlwaysAutoscalingDecider implements AutoscalingDecider {
public class AlwaysAutoscalingDeciderConfiguration implements AutoscalingDeciderConfiguration {
public static final String NAME = "always";
private static final ObjectParser<AlwaysAutoscalingDecider, Void> PARSER = new ObjectParser<>(NAME, AlwaysAutoscalingDecider::new);
private static final ObjectParser<AlwaysAutoscalingDeciderConfiguration, Void> PARSER = new ObjectParser<>(
NAME,
AlwaysAutoscalingDeciderConfiguration::new
);
public static AlwaysAutoscalingDecider parse(final XContentParser parser) {
public static AlwaysAutoscalingDeciderConfiguration parse(final XContentParser parser) {
return PARSER.apply(parser, null);
}
public AlwaysAutoscalingDecider() {}
public AlwaysAutoscalingDeciderConfiguration() {}
@SuppressWarnings("unused")
public AlwaysAutoscalingDecider(final StreamInput in) {
public AlwaysAutoscalingDeciderConfiguration(final StreamInput in) {
}
@ -36,11 +39,6 @@ public class AlwaysAutoscalingDecider implements AutoscalingDecider {
return NAME;
}
@Override
public AutoscalingDecision scale() {
return new AutoscalingDecision(NAME, AutoscalingDecisionType.SCALE_UP, "always");
}
@Override
public String getWriteableName() {
return NAME;

View File

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.inject.Inject;
public class AlwaysAutoscalingDeciderService implements AutoscalingDeciderService<AlwaysAutoscalingDeciderConfiguration> {
@Inject
public AlwaysAutoscalingDeciderService() {}
@Override
public String name() {
return AlwaysAutoscalingDeciderConfiguration.NAME;
}
@Override
public AutoscalingDecision scale(AlwaysAutoscalingDeciderConfiguration decider, AutoscalingDeciderContext context) {
return new AutoscalingDecision(AlwaysAutoscalingDeciderConfiguration.NAME, AutoscalingDecisionType.SCALE_UP, "always");
}
}

View File

@ -12,7 +12,7 @@ import org.elasticsearch.common.xcontent.ToXContentObject;
/**
* Represents an autoscaling decider, a component that determines whether or not to scale.
*/
public interface AutoscalingDecider extends ToXContentObject, NamedWriteable {
public interface AutoscalingDeciderConfiguration extends ToXContentObject, NamedWriteable {
/**
* The name of the autoscaling decider.
@ -20,12 +20,4 @@ public interface AutoscalingDecider extends ToXContentObject, NamedWriteable {
* @return the name
*/
String name();
/**
* Whether or not to scale based on the current state.
*
* @return the autoscaling decision
*/
AutoscalingDecision scale();
}

View File

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.cluster.ClusterState;
public interface AutoscalingDeciderContext {
ClusterState state();
}

View File

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling.decision;
/**
* A service to decide for a specific decider.
*/
public interface AutoscalingDeciderService<D extends AutoscalingDeciderConfiguration> {
/**
* The name of the autoscaling decider.
*
* @return the name
*/
String name();
/**
* Whether or not to scale based on the current state.
*
* @param context provides access to information about current state
* @return the autoscaling decision
*/
AutoscalingDecision scale(D decider, AutoscalingDeciderContext context);
}

View File

@ -0,0 +1,84 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling.decision;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.xpack.autoscaling.Autoscaling;
import org.elasticsearch.xpack.autoscaling.AutoscalingMetadata;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicy;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
public class AutoscalingDecisionService {
private Map<String, AutoscalingDeciderService<? extends AutoscalingDeciderConfiguration>> deciderByName;
public AutoscalingDecisionService(Set<AutoscalingDeciderService<? extends AutoscalingDeciderConfiguration>> deciders) {
assert deciders.size() >= 1; // always have always
this.deciderByName = deciders.stream().collect(Collectors.toMap(AutoscalingDeciderService::name, Function.identity()));
}
public static class Holder {
private final Autoscaling autoscaling;
private final SetOnce<AutoscalingDecisionService> servicesSetOnce = new SetOnce<>();
public Holder(Autoscaling autoscaling) {
this.autoscaling = autoscaling;
}
public AutoscalingDecisionService get() {
// defer constructing services until transport action creation time.
AutoscalingDecisionService autoscalingDecisionService = servicesSetOnce.get();
if (autoscalingDecisionService == null) {
autoscalingDecisionService = new AutoscalingDecisionService(autoscaling.createDeciderServices());
servicesSetOnce.set(autoscalingDecisionService);
}
return autoscalingDecisionService;
}
}
public SortedMap<String, AutoscalingDecisions> decide(ClusterState state) {
AutoscalingDeciderContext context = () -> state;
AutoscalingMetadata autoscalingMetadata = state.metadata().custom(AutoscalingMetadata.NAME);
if (autoscalingMetadata != null) {
return new TreeMap<>(
autoscalingMetadata.policies()
.entrySet()
.stream()
.map(e -> Tuple.tuple(e.getKey(), getDecision(e.getValue().policy(), context)))
.collect(Collectors.toMap(Tuple::v1, Tuple::v2))
);
} else {
return new TreeMap<>();
}
}
private AutoscalingDecisions getDecision(AutoscalingPolicy policy, AutoscalingDeciderContext context) {
Collection<AutoscalingDecision> decisions = policy.deciders()
.values()
.stream()
.map(decider -> getDecision(decider, context))
.collect(Collectors.toList());
return new AutoscalingDecisions(decisions);
}
private <T extends AutoscalingDeciderConfiguration> AutoscalingDecision getDecision(T decider, AutoscalingDeciderContext context) {
assert deciderByName.containsKey(decider.name());
@SuppressWarnings("unchecked")
AutoscalingDeciderService<T> service = (AutoscalingDeciderService<T>) deciderByName.get(decider.name());
return service.scale(decider, context);
}
}

View File

@ -46,9 +46,10 @@ public class AutoscalingDecisions implements ToXContent, Writeable {
@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
for (final AutoscalingDecision decision : decisions) {
decision.toXContent(builder, params);
}
builder.startObject();
builder.field("decision", type());
builder.array("decisions", decisions.toArray());
builder.endObject();
return builder;
}

View File

@ -15,7 +15,7 @@ import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecider;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDeciderConfiguration;
import java.io.IOException;
import java.util.AbstractMap;
@ -39,7 +39,9 @@ public class AutoscalingPolicy extends AbstractDiffable<AutoscalingPolicy> imple
static {
PARSER = new ConstructingObjectParser<>(NAME, false, (c, name) -> {
@SuppressWarnings("unchecked")
final List<Map.Entry<String, AutoscalingDecider>> deciders = (List<Map.Entry<String, AutoscalingDecider>>) c[0];
List<Map.Entry<String, AutoscalingDeciderConfiguration>> deciders =
// help spotless format this
(List<Map.Entry<String, AutoscalingDeciderConfiguration>>) c[0];
return new AutoscalingPolicy(
name,
new TreeMap<>(deciders.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
@ -47,7 +49,7 @@ public class AutoscalingPolicy extends AbstractDiffable<AutoscalingPolicy> imple
});
PARSER.declareNamedObjects(
ConstructingObjectParser.constructorArg(),
(p, c, n) -> new AbstractMap.SimpleEntry<>(n, p.namedObject(AutoscalingDecider.class, n, null)),
(p, c, n) -> new AbstractMap.SimpleEntry<>(n, p.namedObject(AutoscalingDeciderConfiguration.class, n, null)),
DECIDERS_FIELD
);
}
@ -62,13 +64,13 @@ public class AutoscalingPolicy extends AbstractDiffable<AutoscalingPolicy> imple
return name;
}
private final SortedMap<String, AutoscalingDecider> deciders;
private final SortedMap<String, AutoscalingDeciderConfiguration> deciders;
public SortedMap<String, AutoscalingDecider> deciders() {
public SortedMap<String, AutoscalingDeciderConfiguration> deciders() {
return deciders;
}
public AutoscalingPolicy(final String name, final SortedMap<String, AutoscalingDecider> deciders) {
public AutoscalingPolicy(final String name, final SortedMap<String, AutoscalingDeciderConfiguration> deciders) {
this.name = Objects.requireNonNull(name);
// TODO: validate that the policy deciders are non-empty
this.deciders = Objects.requireNonNull(deciders);
@ -77,9 +79,9 @@ public class AutoscalingPolicy extends AbstractDiffable<AutoscalingPolicy> imple
public AutoscalingPolicy(final StreamInput in) throws IOException {
name = in.readString();
deciders = new TreeMap<>(
in.readNamedWriteableList(AutoscalingDecider.class)
in.readNamedWriteableList(AutoscalingDeciderConfiguration.class)
.stream()
.collect(Collectors.toMap(AutoscalingDecider::name, Function.identity()))
.collect(Collectors.toMap(AutoscalingDeciderConfiguration::name, Function.identity()))
);
}
@ -95,7 +97,7 @@ public class AutoscalingPolicy extends AbstractDiffable<AutoscalingPolicy> imple
{
builder.startObject(DECIDERS_FIELD.getPreferredName());
{
for (final Map.Entry<String, AutoscalingDecider> entry : deciders.entrySet()) {
for (final Map.Entry<String, AutoscalingDeciderConfiguration> entry : deciders.entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
}

View File

@ -11,8 +11,8 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.autoscaling.decision.AlwaysAutoscalingDecider;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecider;
import org.elasticsearch.xpack.autoscaling.decision.AlwaysAutoscalingDeciderConfiguration;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDeciderConfiguration;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecision;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisionType;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisions;
@ -68,11 +68,11 @@ public abstract class AutoscalingTestCase extends ESTestCase {
return new AutoscalingDecisions(decisions);
}
public static SortedMap<String, AutoscalingDecider> randomAutoscalingDeciders() {
public static SortedMap<String, AutoscalingDeciderConfiguration> randomAutoscalingDeciders() {
return new TreeMap<>(
org.elasticsearch.common.collect.List.of(new AlwaysAutoscalingDecider())
org.elasticsearch.common.collect.List.of(new AlwaysAutoscalingDeciderConfiguration())
.stream()
.collect(Collectors.toMap(AutoscalingDecider::name, Function.identity()))
.collect(Collectors.toMap(AutoscalingDeciderConfiguration::name, Function.identity()))
);
}
@ -85,7 +85,7 @@ public abstract class AutoscalingTestCase extends ESTestCase {
}
public static AutoscalingPolicy mutateAutoscalingPolicy(final AutoscalingPolicy instance) {
final SortedMap<String, AutoscalingDecider> deciders;
final SortedMap<String, AutoscalingDeciderConfiguration> deciders;
if (randomBoolean()) {
// if the policy name did not change, or randomly, use a mutated set of deciders
deciders = mutateAutoscalingDeciders(instance.deciders());
@ -95,7 +95,9 @@ public abstract class AutoscalingTestCase extends ESTestCase {
return new AutoscalingPolicy(randomValueOtherThan(instance.name(), () -> randomAlphaOfLength(8)), deciders);
}
public static SortedMap<String, AutoscalingDecider> mutateAutoscalingDeciders(final SortedMap<String, AutoscalingDecider> deciders) {
public static SortedMap<String, AutoscalingDeciderConfiguration> mutateAutoscalingDeciders(
final SortedMap<String, AutoscalingDeciderConfiguration> deciders
) {
if (deciders.size() == 0) {
return randomAutoscalingDeciders();
} else {

View File

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling.action;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.xpack.autoscaling.AutoscalingTestCase;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisions;
import org.hamcrest.Matchers;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class GetAutoscalingDecisionActionResponseTests extends AutoscalingTestCase {
public void testToXContent() throws IOException {
Set<String> policyNames = IntStream.range(0, randomIntBetween(1, 10))
.mapToObj(i -> randomAlphaOfLength(10))
.collect(Collectors.toSet());
SortedMap<String, AutoscalingDecisions> decisions = new TreeMap<>(
policyNames.stream().map(s -> Tuple.tuple(s, randomAutoscalingDecisions())).collect(Collectors.toMap(Tuple::v1, Tuple::v2))
);
GetAutoscalingDecisionAction.Response response = new GetAutoscalingDecisionAction.Response(decisions);
XContentType xContentType = randomFrom(XContentType.values());
XContentBuilder builder = XContentBuilder.builder(xContentType.xContent());
response.toXContent(builder, null);
BytesReference responseBytes = BytesReference.bytes(builder);
XContentBuilder expected = XContentBuilder.builder(xContentType.xContent());
expected.startObject();
expected.startArray("decisions");
for (Map.Entry<String, AutoscalingDecisions> entry : decisions.entrySet()) {
expected.startObject();
expected.field(entry.getKey(), entry.getValue());
expected.endObject();
}
expected.endArray();
expected.endObject();
BytesReference expectedBytes = BytesReference.bytes(expected);
assertThat(responseBytes, Matchers.equalTo(expectedBytes));
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.xpack.autoscaling.AutoscalingMetadata;
import org.elasticsearch.xpack.autoscaling.AutoscalingTestCase;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicy;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicyMetadata;
import org.hamcrest.Matchers;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class AutoscalingDecisionServiceTests extends AutoscalingTestCase {
public void testAlwaysDecision() {
AutoscalingDecisionService service = new AutoscalingDecisionService(
org.elasticsearch.common.collect.Set.of(new AlwaysAutoscalingDeciderService())
);
Set<String> policyNames = IntStream.range(0, randomIntBetween(1, 10))
.mapToObj(i -> randomAlphaOfLength(10))
.collect(Collectors.toSet());
SortedMap<String, AutoscalingDeciderConfiguration> deciders = new TreeMap<>(
org.elasticsearch.common.collect.Map.of(AlwaysAutoscalingDeciderConfiguration.NAME, new AlwaysAutoscalingDeciderConfiguration())
);
SortedMap<String, AutoscalingPolicyMetadata> policies = new TreeMap<>(
policyNames.stream()
.map(s -> Tuple.tuple(s, new AutoscalingPolicyMetadata(new AutoscalingPolicy(s, deciders))))
.collect(Collectors.toMap(Tuple::v1, Tuple::v2))
);
ClusterState state = ClusterState.builder(ClusterName.DEFAULT)
.metadata(Metadata.builder().putCustom(AutoscalingMetadata.NAME, new AutoscalingMetadata(policies)))
.build();
SortedMap<String, AutoscalingDecisions> decisions = service.decide(state);
SortedMap<String, AutoscalingDecisions> expected = new TreeMap<>(
policyNames.stream()
.map(
s -> Tuple.tuple(
s,
new AutoscalingDecisions(
org.elasticsearch.common.collect.List.of(
new AutoscalingDecision(
AlwaysAutoscalingDeciderConfiguration.NAME,
AutoscalingDecisionType.SCALE_UP,
"always"
)
)
)
)
)
.collect(Collectors.toMap(Tuple::v1, Tuple::v2))
);
assertThat(decisions, Matchers.equalTo(expected));
}
}