From a155315ceb934274daa8ca1e48d5bd8f030523a5 Mon Sep 17 00:00:00 2001 From: Henning Andersen <33268011+henningandersen@users.noreply.github.com> Date: Mon, 10 Aug 2020 15:28:52 +0200 Subject: [PATCH] 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 --- .../apis/get-autoscaling-policy.asciidoc | 11 +++ .../apis/put-autoscaling-policy.asciidoc | 20 +++++ .../autoscaling/get_autoscaling_decision.yml | 27 +++++- .../autoscaling/put_autoscaling_policy.yml | 5 ++ .../xpack/autoscaling/Autoscaling.java | 71 ++++++++++++++-- .../autoscaling/AutoscalingExtension.java | 20 +++++ ...TransportGetAutoscalingDecisionAction.java | 12 ++- ...lwaysAutoscalingDeciderConfiguration.java} | 18 ++-- .../AlwaysAutoscalingDeciderService.java | 25 ++++++ ...a => AutoscalingDeciderConfiguration.java} | 10 +-- .../decision/AutoscalingDeciderContext.java | 13 +++ .../decision/AutoscalingDeciderService.java | 29 +++++++ .../decision/AutoscalingDecisionService.java | 84 +++++++++++++++++++ .../decision/AutoscalingDecisions.java | 7 +- .../autoscaling/policy/AutoscalingPolicy.java | 20 +++-- .../autoscaling/AutoscalingTestCase.java | 16 ++-- ...utoscalingDecisionActionResponseTests.java | 56 +++++++++++++ .../AutoscalingDecisionServiceTests.java | 65 ++++++++++++++ 18 files changed, 458 insertions(+), 51 deletions(-) create mode 100644 x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/AutoscalingExtension.java rename x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/{AlwaysAutoscalingDecider.java => AlwaysAutoscalingDeciderConfiguration.java} (73%) create mode 100644 x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderService.java rename x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/{AutoscalingDecider.java => AutoscalingDeciderConfiguration.java} (72%) create mode 100644 x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderContext.java create mode 100644 x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderService.java create mode 100644 x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionService.java create mode 100644 x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/action/GetAutoscalingDecisionActionResponseTests.java create mode 100644 x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionServiceTests.java diff --git a/docs/reference/autoscaling/apis/get-autoscaling-policy.asciidoc b/docs/reference/autoscaling/apis/get-autoscaling-policy.asciidoc index 94986531817..0aba86a08ea 100644 --- a/docs/reference/autoscaling/apis/get-autoscaling-policy.asciidoc +++ b/docs/reference/autoscaling/apis/get-autoscaling-policy.asciidoc @@ -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/ diff --git a/docs/reference/autoscaling/apis/put-autoscaling-policy.asciidoc b/docs/reference/autoscaling/apis/put-autoscaling-policy.asciidoc index c4e46f06511..31953b1466a 100644 --- a/docs/reference/autoscaling/apis/put-autoscaling-policy.asciidoc +++ b/docs/reference/autoscaling/apis/put-autoscaling-policy.asciidoc @@ -25,6 +25,16 @@ PUT /_autoscaling/policy/ -------------------------------------------------- // TEST[s//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] + +////////////////////////// diff --git a/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/get_autoscaling_decision.yml b/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/get_autoscaling_decision.yml index 27fcac07841..7b667c8fdfc 100644 --- a/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/get_autoscaling_decision.yml +++ b/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/get_autoscaling_decision.yml @@ -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 diff --git a/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/put_autoscaling_policy.yml b/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/put_autoscaling_policy.yml index b7213c64fde..37a2f3df06a 100644 --- a/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/put_autoscaling_policy.yml +++ b/x-pack/plugin/autoscaling/qa/rest/src/test/resources/rest-api-spec/test/autoscaling/put_autoscaling_policy.yml @@ -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: diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/Autoscaling.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/Autoscaling.java index 01802942bf9..8baa42a2804 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/Autoscaling.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/Autoscaling.java @@ -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 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 createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + return org.elasticsearch.common.collect.List.of(new AutoscalingDecisionService.Holder(this)); + } + @Override public List> 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> deciders() { + return org.elasticsearch.common.collect.List.of(new AlwaysAutoscalingDeciderService()); + } + + public Set> createDeciderServices() { + return autoscalingExtensions.stream().flatMap(p -> p.deciders().stream()).collect(Collectors.toSet()); + } } diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/AutoscalingExtension.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/AutoscalingExtension.java new file mode 100644 index 00000000000..a5d05c6e389 --- /dev/null +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/AutoscalingExtension.java @@ -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> deciders(); +} diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/action/TransportGetAutoscalingDecisionAction.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/action/TransportGetAutoscalingDecisionAction.java index 66440c4fab5..b600480555f 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/action/TransportGetAutoscalingDecisionAction.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/action/TransportGetAutoscalingDecisionAction.java @@ -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 listener ) { - listener.onResponse(new GetAutoscalingDecisionAction.Response(Collections.unmodifiableSortedMap(new TreeMap<>()))); + listener.onResponse(new GetAutoscalingDecisionAction.Response(decisionService.decide(state))); } @Override diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDecider.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderConfiguration.java similarity index 73% rename from x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDecider.java rename to x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderConfiguration.java index ef9e1ac0f0a..56780569113 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDecider.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderConfiguration.java @@ -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 PARSER = new ObjectParser<>(NAME, AlwaysAutoscalingDecider::new); + private static final ObjectParser 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; diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderService.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderService.java new file mode 100644 index 00000000000..5ba887597f3 --- /dev/null +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AlwaysAutoscalingDeciderService.java @@ -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 { + + @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"); + } +} diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecider.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderConfiguration.java similarity index 72% rename from x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecider.java rename to x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderConfiguration.java index 28a9f7147bb..4f5b6a71b4f 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecider.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderConfiguration.java @@ -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(); - } diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderContext.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderContext.java new file mode 100644 index 00000000000..00436cd30a7 --- /dev/null +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderContext.java @@ -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(); +} diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderService.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderService.java new file mode 100644 index 00000000000..9928b8b4114 --- /dev/null +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDeciderService.java @@ -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 { + + /** + * 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); + +} diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionService.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionService.java new file mode 100644 index 00000000000..89b0a6abae2 --- /dev/null +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionService.java @@ -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> deciderByName; + + public AutoscalingDecisionService(Set> 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 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 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 decisions = policy.deciders() + .values() + .stream() + .map(decider -> getDecision(decider, context)) + .collect(Collectors.toList()); + return new AutoscalingDecisions(decisions); + } + + private AutoscalingDecision getDecision(T decider, AutoscalingDeciderContext context) { + assert deciderByName.containsKey(decider.name()); + @SuppressWarnings("unchecked") + AutoscalingDeciderService service = (AutoscalingDeciderService) deciderByName.get(decider.name()); + return service.scale(decider, context); + } +} diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisions.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisions.java index ee7e8f4b216..37749e18cb8 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisions.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisions.java @@ -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; } diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/policy/AutoscalingPolicy.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/policy/AutoscalingPolicy.java index 3156358b238..60329badd0c 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/policy/AutoscalingPolicy.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/policy/AutoscalingPolicy.java @@ -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 imple static { PARSER = new ConstructingObjectParser<>(NAME, false, (c, name) -> { @SuppressWarnings("unchecked") - final List> deciders = (List>) c[0]; + List> deciders = + // help spotless format this + (List>) 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 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 imple return name; } - private final SortedMap deciders; + private final SortedMap deciders; - public SortedMap deciders() { + public SortedMap deciders() { return deciders; } - public AutoscalingPolicy(final String name, final SortedMap deciders) { + public AutoscalingPolicy(final String name, final SortedMap 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 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 imple { builder.startObject(DECIDERS_FIELD.getPreferredName()); { - for (final Map.Entry entry : deciders.entrySet()) { + for (final Map.Entry entry : deciders.entrySet()) { builder.field(entry.getKey(), entry.getValue()); } } diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/AutoscalingTestCase.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/AutoscalingTestCase.java index 039b3d868e8..ea9f6c03287 100644 --- a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/AutoscalingTestCase.java +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/AutoscalingTestCase.java @@ -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 randomAutoscalingDeciders() { + public static SortedMap 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 deciders; + final SortedMap 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 mutateAutoscalingDeciders(final SortedMap deciders) { + public static SortedMap mutateAutoscalingDeciders( + final SortedMap deciders + ) { if (deciders.size() == 0) { return randomAutoscalingDeciders(); } else { diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/action/GetAutoscalingDecisionActionResponseTests.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/action/GetAutoscalingDecisionActionResponseTests.java new file mode 100644 index 00000000000..5cedfb3154e --- /dev/null +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/action/GetAutoscalingDecisionActionResponseTests.java @@ -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 policyNames = IntStream.range(0, randomIntBetween(1, 10)) + .mapToObj(i -> randomAlphaOfLength(10)) + .collect(Collectors.toSet()); + + SortedMap 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 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)); + } +} diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionServiceTests.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionServiceTests.java new file mode 100644 index 00000000000..c9c141ab10b --- /dev/null +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/decision/AutoscalingDecisionServiceTests.java @@ -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 policyNames = IntStream.range(0, randomIntBetween(1, 10)) + .mapToObj(i -> randomAlphaOfLength(10)) + .collect(Collectors.toSet()); + SortedMap deciders = new TreeMap<>( + org.elasticsearch.common.collect.Map.of(AlwaysAutoscalingDeciderConfiguration.NAME, new AlwaysAutoscalingDeciderConfiguration()) + ); + SortedMap 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 decisions = service.decide(state); + SortedMap 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)); + } +}