Introduce autoscaling policies (#54473)

This commit is the first in a series of commits that introduces
autoscaling policies, and APIs for working with them. For now, we
introduce the basic infrastructure, and a single API for putting an
autoscaling policy. We will follow in rapid succession with APIs for
getting, and deleting autoscaling policies.
This commit is contained in:
Jason Tedor 2020-04-01 07:35:45 -04:00
parent 6d976e1468
commit f670ae0bc8
No known key found for this signature in database
GPG Key ID: FA89F05560F16BC5
32 changed files with 1521 additions and 20 deletions

View File

@ -10,6 +10,8 @@ You can use the following APIs to perform autoscaling operations.
=== Top-Level
* <<autoscaling-get-autoscaling-decision,Get autoscaling decision>>
* <<autoscaling-put-autoscaling-policy,Put autoscaling policy>>
// top-level
include::get-autoscaling-decision.asciidoc[]
include::put-autoscaling-policy.asciidoc[]

View File

@ -0,0 +1,67 @@
[role="xpack"]
[testenv="platinum"]
[[autoscaling-put-autoscaling-policy]]
=== Put autoscaling policy API
++++
<titleabbrev>Put autoscaling policy</titleabbrev>
++++
Put autoscaling policy.
[[autoscaling-put-autoscaling-policy-request]]
==== {api-request-title}
[source,console]
--------------------------------------------------
PUT /_autoscaling/policy/<name>
{
"policy": {
"deciders": {
"always": {
}
}
}
}
--------------------------------------------------
// TEST[s/<name>/name/]
[[autoscaling-put-autoscaling-policy-prereqs]]
==== {api-prereq-title}
* If the {es} {security-features} are enabled, you must have
`manage_autoscaling` cluster privileges. For more information, see
<<security-privileges>>.
[[autoscaling-put-autoscaling-policy-desc]]
==== {api-description-title}
This API puts an autoscaling policy with the provided name.
[[autoscaling-put-autoscaling-policy-examples]]
==== {api-examples-title}
This example puts an autoscaling policy named `hot` using the always
autoscaling decider.
[source,console]
--------------------------------------------------
PUT /_autoscaling/policy/hot
{
"policy": {
"deciders": {
"always": {
}
}
}
}
--------------------------------------------------
// TEST
The API returns the following result:
[source,console-result]
--------------------------------------------------
{
"acknowledged": true
}
--------------------------------------------------

View File

@ -14,6 +14,16 @@ archivesBaseName = 'x-pack-autoscaling'
integTest.enabled = false
task internalClusterTest(type: Test) {
description = 'Java fantasy integration tests'
mustRunAfter test
include '**/*IT.class'
systemProperty 'es.set.netty.runtime.available.processors', 'false'
}
check.dependsOn internalClusterTest
dependencies {
compileOnly project(path: xpackModule('core'), configuration: 'default')
testCompile project(path: xpackModule('core'), configuration: 'testArtifacts')
@ -27,3 +37,9 @@ gradle.projectsEvaluated {
.findAll { it.path.startsWith(project.path + ":qa") }
.each { check.dependsOn it.check }
}
testingConventions.naming {
IT {
baseClass "org.elasticsearch.xpack.autoscaling.AutoscalingIntegTestCase"
}
}

View File

@ -0,0 +1,21 @@
---
"Test put autoscaling decision":
- do:
autoscaling.put_autoscaling_policy:
name: hot
body:
policy:
deciders:
always: {}
- match: { "acknowledged": true }
- do:
catch: bad_request
autoscaling.put_autoscaling_policy:
name: hot
body:
policy:
deciders:
does_not_exist: {}

View File

@ -9,21 +9,34 @@ package org.elasticsearch.xpack.autoscaling;
import org.elasticsearch.Build;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
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.common.ParseField;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
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.license.XPackLicenseState;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.xpack.autoscaling.action.GetAutoscalingDecisionAction;
import org.elasticsearch.xpack.autoscaling.action.PutAutoscalingPolicyAction;
import org.elasticsearch.xpack.autoscaling.action.TransportGetAutoscalingDecisionAction;
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.rest.RestGetAutoscalingDecisionHandler;
import org.elasticsearch.xpack.autoscaling.rest.RestPutAutoscalingPolicyHandler;
import org.elasticsearch.xpack.core.XPackPlugin;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@ -79,11 +92,18 @@ public class Autoscaling extends Plugin implements ActionPlugin {
}
}
boolean isSnapshot() {
return Build.CURRENT.isSnapshot();
}
@Override
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
if (enabled) {
return Collections.singletonList(
new ActionHandler<>(GetAutoscalingDecisionAction.INSTANCE, TransportGetAutoscalingDecisionAction.class)
return Collections.unmodifiableList(
Arrays.asList(
new ActionHandler<>(GetAutoscalingDecisionAction.INSTANCE, TransportGetAutoscalingDecisionAction.class),
new ActionHandler<>(PutAutoscalingPolicyAction.INSTANCE, TransportPutAutoscalingPolicyAction.class)
)
);
} else {
return Collections.emptyList();
@ -101,14 +121,49 @@ public class Autoscaling extends Plugin implements ActionPlugin {
final Supplier<DiscoveryNodes> nodesInCluster
) {
if (enabled) {
return Collections.singletonList(new RestGetAutoscalingDecisionHandler());
return Collections.unmodifiableList(
Arrays.asList(new RestGetAutoscalingDecisionHandler(), new RestPutAutoscalingPolicyHandler())
);
} else {
return Collections.emptyList();
}
}
boolean isSnapshot() {
return Build.CURRENT.isSnapshot();
@Override
public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
return Collections.unmodifiableList(
Arrays.asList(
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)
)
);
}
@Override
public List<NamedXContentRegistry.Entry> getNamedXContent() {
return Collections.unmodifiableList(
Arrays.asList(
new NamedXContentRegistry.Entry(
Metadata.Custom.class,
new ParseField(AutoscalingMetadata.NAME),
AutoscalingMetadata::parse
),
new NamedXContentRegistry.Entry(
AutoscalingDecider.class,
new ParseField(AlwaysAutoscalingDecider.NAME),
AlwaysAutoscalingDecider::parse
)
)
);
}
protected XPackLicenseState getLicenseState() {
return XPackPlugin.getSharedLicenseState();
}
}

View File

@ -0,0 +1,169 @@
/*
* 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.Version;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicyMetadata;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
public class AutoscalingMetadata implements Metadata.Custom {
public static final String NAME = "autoscaling";
public static final AutoscalingMetadata EMPTY = new AutoscalingMetadata(Collections.emptySortedMap());
private static final ParseField POLICIES_FIELD = new ParseField("policies");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<AutoscalingMetadata, Void> PARSER = new ConstructingObjectParser<>(
NAME,
c -> new AutoscalingMetadata(
new TreeMap<>(
((List<AutoscalingPolicyMetadata>) c[0]).stream().collect(Collectors.toMap(p -> p.policy().name(), Function.identity()))
)
)
);
static {
PARSER.declareNamedObjects(
ConstructingObjectParser.constructorArg(),
(p, c, n) -> AutoscalingPolicyMetadata.parse(p, n),
POLICIES_FIELD
);
}
public static AutoscalingMetadata parse(final XContentParser parser) {
return PARSER.apply(parser, null);
}
private final SortedMap<String, AutoscalingPolicyMetadata> policies;
public SortedMap<String, AutoscalingPolicyMetadata> policies() {
return policies;
}
public AutoscalingMetadata(final SortedMap<String, AutoscalingPolicyMetadata> policies) {
this.policies = policies;
}
public AutoscalingMetadata(final StreamInput in) throws IOException {
final int size = in.readVInt();
final SortedMap<String, AutoscalingPolicyMetadata> policies = new TreeMap<>();
for (int i = 0; i < size; i++) {
final AutoscalingPolicyMetadata policyMetadata = new AutoscalingPolicyMetadata(in);
policies.put(policyMetadata.policy().name(), policyMetadata);
}
this.policies = policies;
}
@Override
public void writeTo(final StreamOutput out) throws IOException {
out.writeVInt(policies.size());
for (final Map.Entry<String, AutoscalingPolicyMetadata> policy : policies.entrySet()) {
policy.getValue().writeTo(out);
}
}
@Override
public EnumSet<Metadata.XContentContext> context() {
return Metadata.ALL_CONTEXTS;
}
@Override
public Diff<Metadata.Custom> diff(final Metadata.Custom previousState) {
return new AutoscalingMetadataDiff((AutoscalingMetadata) previousState, this);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public Version getMinimalSupportedVersion() {
return Version.V_7_8_0;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(POLICIES_FIELD.getPreferredName(), policies);
return builder;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AutoscalingMetadata metadata = (AutoscalingMetadata) o;
return policies.equals(metadata.policies);
}
@Override
public int hashCode() {
return Objects.hash(policies);
}
public static class AutoscalingMetadataDiff implements NamedDiff<Metadata.Custom> {
final Diff<Map<String, AutoscalingPolicyMetadata>> policies;
public AutoscalingMetadataDiff(final AutoscalingMetadata before, final AutoscalingMetadata after) {
this.policies = DiffableUtils.diff(before.policies, after.policies, DiffableUtils.getStringKeySerializer());
}
public AutoscalingMetadataDiff(final StreamInput in) throws IOException {
this.policies = DiffableUtils.readJdkMapDiff(
in,
DiffableUtils.getStringKeySerializer(),
AutoscalingPolicyMetadata::new,
AutoscalingMetadataDiff::readFrom
);
}
@Override
public Metadata.Custom apply(final Metadata.Custom part) {
return new AutoscalingMetadata(new TreeMap<>(policies.apply(((AutoscalingMetadata) part).policies)));
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(final StreamOutput out) throws IOException {
policies.writeTo(out);
}
static Diff<AutoscalingPolicyMetadata> readFrom(final StreamInput in) throws IOException {
return AbstractDiffable.readDiffFrom(AutoscalingPolicyMetadata::new, in);
}
}
}

View File

@ -14,7 +14,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.autoscaling.AutoscalingDecisions;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisions;
import java.io.IOException;
import java.util.Map;

View File

@ -0,0 +1,90 @@
/*
* 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.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.support.master.AcknowledgedRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
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.policy.AutoscalingPolicy;
import java.io.IOException;
public class PutAutoscalingPolicyAction extends ActionType<AcknowledgedResponse> {
public static final PutAutoscalingPolicyAction INSTANCE = new PutAutoscalingPolicyAction();
public static final String NAME = "cluster:admin/autoscaling/put_autoscaling_policy";
private PutAutoscalingPolicyAction() {
super(NAME, AcknowledgedResponse::new);
}
public static class Request extends AcknowledgedRequest<Request> implements ToXContentObject {
static final ParseField POLICY_FIELD = new ParseField("policy");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Request, String> PARSER = new ConstructingObjectParser<>(
"put_autoscaling_policy_request",
a -> new Request((AutoscalingPolicy) a[0])
);
static {
PARSER.declareObject(ConstructingObjectParser.constructorArg(), AutoscalingPolicy::parse, POLICY_FIELD);
}
public static Request parse(final XContentParser parser, final String name) {
return PARSER.apply(parser, name);
}
private final AutoscalingPolicy policy;
public AutoscalingPolicy policy() {
return policy;
}
public Request(final AutoscalingPolicy policy) {
this.policy = policy;
}
public Request(final StreamInput in) throws IOException {
super(in);
policy = new AutoscalingPolicy(in);
}
@Override
public void writeTo(final StreamOutput out) throws IOException {
super.writeTo(out);
policy.writeTo(out);
}
@Override
public ActionRequestValidationException validate() {
// TODO: validate that the policy deciders are non-empty
return null;
}
@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
{
builder.field(POLICY_FIELD.getPreferredName(), policy);
}
builder.endObject();
return builder;
}
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.service.ClusterService;
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.AutoscalingMetadata;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicy;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicyMetadata;
import java.io.IOException;
import java.util.SortedMap;
import java.util.TreeMap;
public class TransportPutAutoscalingPolicyAction extends TransportMasterNodeAction<
PutAutoscalingPolicyAction.Request,
AcknowledgedResponse> {
private static final Logger logger = LogManager.getLogger(TransportPutAutoscalingPolicyAction.class);
@Inject
public TransportPutAutoscalingPolicyAction(
final TransportService transportService,
final ClusterService clusterService,
final ThreadPool threadPool,
final ActionFilters actionFilters,
final IndexNameExpressionResolver indexNameExpressionResolver
) {
super(
PutAutoscalingPolicyAction.NAME,
transportService,
clusterService,
threadPool,
actionFilters,
PutAutoscalingPolicyAction.Request::new,
indexNameExpressionResolver
);
}
@Override
protected String executor() {
return ThreadPool.Names.SAME;
}
@Override
protected AcknowledgedResponse read(final StreamInput in) throws IOException {
return new AcknowledgedResponse(in);
}
@Override
protected void masterOperation(
final PutAutoscalingPolicyAction.Request request,
final ClusterState state,
ActionListener<AcknowledgedResponse> listener
) {
clusterService.submitStateUpdateTask(
"put-autoscaling-policy",
new AckedClusterStateUpdateTask<AcknowledgedResponse>(request, listener) {
@Override
protected AcknowledgedResponse newResponse(final boolean acknowledged) {
return new AcknowledgedResponse(acknowledged);
}
@Override
public ClusterState execute(final ClusterState currentState) {
return putAutoscalingPolicy(currentState, request.policy(), logger);
}
}
);
}
@Override
protected ClusterBlockException checkBlock(final PutAutoscalingPolicyAction.Request request, final ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
}
static ClusterState putAutoscalingPolicy(final ClusterState currentState, final AutoscalingPolicy policy, final Logger logger) {
final ClusterState.Builder builder = ClusterState.builder(currentState);
final AutoscalingMetadata currentMetadata;
if (currentState.metadata().custom(AutoscalingMetadata.NAME) != null) {
currentMetadata = currentState.metadata().custom(AutoscalingMetadata.NAME);
} else {
currentMetadata = AutoscalingMetadata.EMPTY;
}
final SortedMap<String, AutoscalingPolicyMetadata> newPolicies = new TreeMap<>(currentMetadata.policies());
final AutoscalingPolicyMetadata newPolicyMetadata = new AutoscalingPolicyMetadata(policy);
final AutoscalingPolicyMetadata oldPolicyMetadata = newPolicies.put(policy.name(), newPolicyMetadata);
if (oldPolicyMetadata == null) {
logger.info("adding autoscaling policy [{}]", policy.name());
} else if (oldPolicyMetadata.equals(newPolicyMetadata)) {
logger.info("skipping updating autoscaling policy [{}] due to no change in policy", policy.name());
return currentState;
} else {
logger.info("updating autoscaling policy [{}]", policy.name());
}
final AutoscalingMetadata newMetadata = new AutoscalingMetadata(newPolicies);
builder.metadata(Metadata.builder(currentState.getMetadata()).putCustom(AutoscalingMetadata.NAME, newMetadata).build());
return builder.build();
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
public class AlwaysAutoscalingDecider implements AutoscalingDecider {
public static final String NAME = "always";
private static final ObjectParser<AlwaysAutoscalingDecider, Void> PARSER = new ObjectParser<>(NAME, AlwaysAutoscalingDecider::new);
public static AlwaysAutoscalingDecider parse(final XContentParser parser) {
return PARSER.apply(parser, null);
}
public AlwaysAutoscalingDecider() {}
@SuppressWarnings("unused")
public AlwaysAutoscalingDecider(final StreamInput in) {
}
@Override
public String name() {
return NAME;
}
@Override
public AutoscalingDecision scale() {
return new AutoscalingDecision(NAME, AutoscalingDecisionType.SCALE_UP, "always");
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(final StreamOutput out) {
}
@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
{}
builder.endObject();
return builder;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return true;
}
@Override
public int hashCode() {
return 0;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.io.stream.NamedWriteable;
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 {
/**
* The name of the autoscaling decider.
*
* @return the name
*/
String name();
/**
* Whether or not to scale based on the current state.
*
* @return the autoscaling decision
*/
AutoscalingDecision scale();
}

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@ -65,7 +65,7 @@ public class AutoscalingDecisions implements ToXContent, Writeable {
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final org.elasticsearch.xpack.autoscaling.AutoscalingDecisions that = (org.elasticsearch.xpack.autoscaling.AutoscalingDecisions) o;
final AutoscalingDecisions that = (AutoscalingDecisions) o;
return decisions.equals(that.decisions);
}

View File

@ -0,0 +1,121 @@
/*
* 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.policy;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.Diffable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
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 java.io.IOException;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
public class AutoscalingPolicy extends AbstractDiffable<AutoscalingPolicy> implements Diffable<AutoscalingPolicy>, ToXContentObject {
public static final String NAME = "autoscaling_policy";
public static final ParseField DECIDERS_FIELD = new ParseField("deciders");
private static final ConstructingObjectParser<AutoscalingPolicy, String> PARSER;
static {
PARSER = new ConstructingObjectParser<>(NAME, false, (c, name) -> {
@SuppressWarnings("unchecked")
final List<Map.Entry<String, AutoscalingDecider>> deciders = (List<Map.Entry<String, AutoscalingDecider>>) c[0];
return new AutoscalingPolicy(
name,
new TreeMap<>(deciders.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
);
});
PARSER.declareNamedObjects(
ConstructingObjectParser.constructorArg(),
(p, c, n) -> new AbstractMap.SimpleEntry<>(n, p.namedObject(AutoscalingDecider.class, n, null)),
DECIDERS_FIELD
);
}
public static AutoscalingPolicy parse(final XContentParser parser, final String name) {
return PARSER.apply(parser, name);
}
private final String name;
public String name() {
return name;
}
private final SortedMap<String, AutoscalingDecider> deciders;
public SortedMap<String, AutoscalingDecider> deciders() {
return deciders;
}
public AutoscalingPolicy(final String name, final SortedMap<String, AutoscalingDecider> deciders) {
this.name = Objects.requireNonNull(name);
// TODO: validate that the policy deciders are non-empty
this.deciders = Objects.requireNonNull(deciders);
}
public AutoscalingPolicy(final StreamInput in) throws IOException {
name = in.readString();
deciders = new TreeMap<>(
in.readNamedWriteableList(AutoscalingDecider.class)
.stream()
.collect(Collectors.toMap(AutoscalingDecider::name, Function.identity()))
);
}
@Override
public void writeTo(final StreamOutput out) throws IOException {
out.writeString(name);
out.writeNamedWriteableList(Collections.unmodifiableList(deciders.values().stream().collect(Collectors.toList())));
}
@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
{
builder.startObject(DECIDERS_FIELD.getPreferredName());
{
for (final Map.Entry<String, AutoscalingDecider> entry : deciders.entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
}
builder.endObject();
}
builder.endObject();
return builder;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AutoscalingPolicy that = (AutoscalingPolicy) o;
return name.equals(that.name) && deciders.equals(that.deciders);
}
@Override
public int hashCode() {
return Objects.hash(name, deciders);
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.policy;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.Diffable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
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 java.io.IOException;
import java.util.Objects;
public class AutoscalingPolicyMetadata extends AbstractDiffable<AutoscalingPolicyMetadata>
implements
Diffable<AutoscalingPolicyMetadata>,
ToXContentObject {
static final ParseField POLICY_FIELD = new ParseField("policy");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<AutoscalingPolicyMetadata, String> PARSER;
static {
PARSER = new ConstructingObjectParser<>("autoscaling_policy_metadata", a -> {
final AutoscalingPolicy policy = (AutoscalingPolicy) a[0];
return new AutoscalingPolicyMetadata(policy);
});
PARSER.declareObject(ConstructingObjectParser.constructorArg(), AutoscalingPolicy::parse, POLICY_FIELD);
}
public static AutoscalingPolicyMetadata parse(final XContentParser parser, final String name) {
return PARSER.apply(parser, name);
}
private final AutoscalingPolicy policy;
public AutoscalingPolicy policy() {
return policy;
}
public AutoscalingPolicyMetadata(final AutoscalingPolicy policy) {
this.policy = policy;
}
public AutoscalingPolicyMetadata(final StreamInput in) throws IOException {
policy = new AutoscalingPolicy(in);
}
@Override
public void writeTo(final StreamOutput out) throws IOException {
policy.writeTo(out);
}
@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
{
builder.field(POLICY_FIELD.getPreferredName(), policy);
}
builder.endObject();
return builder;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AutoscalingPolicyMetadata that = (AutoscalingPolicyMetadata) o;
return policy.equals(that.policy);
}
@Override
public int hashCode() {
return Objects.hash(policy);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.rest;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.xpack.autoscaling.action.PutAutoscalingPolicyAction;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutAutoscalingPolicyHandler extends BaseRestHandler {
@Override
public List<Route> routes() {
return Collections.singletonList(new Route(PUT, "/_autoscaling/policy/{name}"));
}
@Override
public String getName() {
return "put_autoscaling_policy";
}
@Override
protected RestChannelConsumer prepareRequest(final RestRequest restRequest, final NodeClient client) throws IOException {
final String name = restRequest.param("name");
final PutAutoscalingPolicyAction.Request request;
try (XContentParser parser = restRequest.contentParser()) {
request = PutAutoscalingPolicyAction.Request.parse(parser, name);
}
return channel -> client.execute(PutAutoscalingPolicyAction.INSTANCE, request, new RestToXContentListener<>(channel));
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.common.settings.Settings;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.xpack.core.XPackSettings;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
public abstract class AutoscalingIntegTestCase extends ESIntegTestCase {
@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Collections.singletonList(LocalStateAutoscaling.class);
}
@Override
protected Settings nodeSettings(final int nodeOrdinal) {
final Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal));
builder.put(Autoscaling.AUTOSCALING_ENABLED_SETTING.getKey(), true);
builder.put(XPackSettings.SECURITY_ENABLED.getKey(), false);
return builder.build();
}
@Override
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
return Collections.unmodifiableList(Arrays.asList(LocalStateAutoscaling.class, getTestTransportPlugin()));
}
@Override
protected Settings transportClientSettings() {
final Settings.Builder builder = Settings.builder().put(super.transportClientSettings());
builder.put(Autoscaling.AUTOSCALING_ENABLED_SETTING.getKey(), true);
builder.put(XPackSettings.SECURITY_ENABLED.getKey(), false);
return builder.build();
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.cluster.Diff;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractDiffableSerializationTestCase;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicy;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicyMetadata;
import java.io.IOException;
import java.util.SortedMap;
import java.util.TreeMap;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.mutateAutoscalingPolicy;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.randomAutoscalingMetadata;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.randomAutoscalingPolicy;
public class AutoscalingMetadataDiffableSerializationTests extends AbstractDiffableSerializationTestCase<Metadata.Custom> {
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(new Autoscaling(Settings.EMPTY).getNamedWriteables());
}
@Override
protected NamedXContentRegistry xContentRegistry() {
return new NamedXContentRegistry(new Autoscaling(Settings.EMPTY).getNamedXContent());
}
@Override
protected AutoscalingMetadata doParseInstance(final XContentParser parser) throws IOException {
return AutoscalingMetadata.parse(parser);
}
@Override
protected Writeable.Reader<Metadata.Custom> instanceReader() {
return AutoscalingMetadata::new;
}
@Override
protected AutoscalingMetadata createTestInstance() {
return randomAutoscalingMetadata();
}
@Override
protected Metadata.Custom makeTestChanges(final Metadata.Custom testInstance) {
return mutateInstance(testInstance);
}
@Override
protected Metadata.Custom mutateInstance(final Metadata.Custom instance) {
final AutoscalingMetadata metadata = (AutoscalingMetadata) instance;
final SortedMap<String, AutoscalingPolicyMetadata> policies = new TreeMap<>(metadata.policies());
if (policies.size() == 0 || randomBoolean()) {
final AutoscalingPolicy policy = randomAutoscalingPolicy();
policies.put(policy.name(), new AutoscalingPolicyMetadata(policy));
} else {
// randomly remove a policy
final String name = randomFrom(policies.keySet());
final AutoscalingPolicyMetadata policyMetadata = policies.remove(name);
final AutoscalingPolicy mutatedPolicy = mutateAutoscalingPolicy(policyMetadata.policy());
policies.put(mutatedPolicy.name(), new AutoscalingPolicyMetadata(mutatedPolicy));
}
return new AutoscalingMetadata(policies);
}
@Override
protected Writeable.Reader<Diff<Metadata.Custom>> diffReader() {
return AutoscalingMetadata.AutoscalingMetadataDiff::new;
}
}

View File

@ -8,21 +8,34 @@ package org.elasticsearch.xpack.autoscaling;
import org.elasticsearch.common.Randomness;
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.AutoscalingDecision;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisionType;
import org.elasticsearch.xpack.autoscaling.decision.AutoscalingDecisions;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicy;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicyMetadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class AutoscalingTestCase extends ESTestCase {
static AutoscalingDecision randomAutoscalingDecision() {
public static AutoscalingDecision randomAutoscalingDecision() {
return randomAutoscalingDecisionOfType(randomFrom(AutoscalingDecisionType.values()));
}
static AutoscalingDecision randomAutoscalingDecisionOfType(final AutoscalingDecisionType type) {
public static AutoscalingDecision randomAutoscalingDecisionOfType(final AutoscalingDecisionType type) {
return new AutoscalingDecision(randomAlphaOfLength(8), type, randomAlphaOfLength(8));
}
static AutoscalingDecisions randomAutoscalingDecisions() {
public static AutoscalingDecisions randomAutoscalingDecisions() {
final int numberOfDecisions = 1 + randomIntBetween(1, 8);
final List<AutoscalingDecision> decisions = new ArrayList<>(numberOfDecisions);
for (int i = 0; i < numberOfDecisions; i++) {
@ -34,7 +47,7 @@ public abstract class AutoscalingTestCase extends ESTestCase {
return randomAutoscalingDecisions(numberOfDownDecisions, numberOfNoDecisions, numberOfUpDecisions);
}
static AutoscalingDecisions randomAutoscalingDecisions(
public static AutoscalingDecisions randomAutoscalingDecisions(
final int numberOfDownDecisions,
final int numberOfNoDecisions,
final int numberOfUpDecisions
@ -53,4 +66,57 @@ public abstract class AutoscalingTestCase extends ESTestCase {
return new AutoscalingDecisions(decisions);
}
public static SortedMap<String, AutoscalingDecider> randomAutoscalingDeciders() {
return new TreeMap<>(
Collections.singletonList(new AlwaysAutoscalingDecider())
.stream()
.collect(Collectors.toMap(AutoscalingDecider::name, Function.identity()))
);
}
public static AutoscalingPolicy randomAutoscalingPolicy() {
return randomAutoscalingPolicyOfName(randomAlphaOfLength(8));
}
public static AutoscalingPolicy randomAutoscalingPolicyOfName(final String name) {
return new AutoscalingPolicy(name, randomAutoscalingDeciders());
}
public static AutoscalingPolicy mutateAutoscalingPolicy(final AutoscalingPolicy instance) {
final SortedMap<String, AutoscalingDecider> deciders;
if (randomBoolean()) {
// if the policy name did not change, or randomly, use a mutated set of deciders
deciders = mutateAutoscalingDeciders(instance.deciders());
} else {
deciders = instance.deciders();
}
return new AutoscalingPolicy(randomValueOtherThan(instance.name(), () -> randomAlphaOfLength(8)), deciders);
}
public static SortedMap<String, AutoscalingDecider> mutateAutoscalingDeciders(final SortedMap<String, AutoscalingDecider> deciders) {
if (deciders.size() == 0) {
return randomAutoscalingDeciders();
} else {
// use a proper subset of the deciders
return new TreeMap<>(
randomSubsetOf(randomIntBetween(0, deciders.size() - 1), deciders.entrySet()).stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
);
}
}
public static AutoscalingMetadata randomAutoscalingMetadata() {
return randomAutoscalingMetadataOfPolicyCount(randomIntBetween(0, 8));
}
public static AutoscalingMetadata randomAutoscalingMetadataOfPolicyCount(final int numberOfPolicies) {
final SortedMap<String, AutoscalingPolicyMetadata> policies = new TreeMap<>();
for (int i = 0; i < numberOfPolicies; i++) {
final AutoscalingPolicy policy = randomAutoscalingPolicy();
final AutoscalingPolicyMetadata policyMetadata = new AutoscalingPolicyMetadata(policy);
policies.put(policy.name(), policyMetadata);
}
return new AutoscalingMetadata(policies);
}
}

View File

@ -0,0 +1,27 @@
/*
* 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.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
public class LocalStateAutoscaling extends LocalStateCompositeXPackPlugin {
public LocalStateAutoscaling(final Settings settings) {
super(settings, null);
plugins.add(new Autoscaling(settings) {
@Override
protected XPackLicenseState getLicenseState() {
return LocalStateAutoscaling.this.getLicenseState();
}
});
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.cluster.ClusterState;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.xpack.autoscaling.AutoscalingIntegTestCase;
import org.elasticsearch.xpack.autoscaling.AutoscalingMetadata;
import org.elasticsearch.xpack.autoscaling.policy.AutoscalingPolicy;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.mutateAutoscalingDeciders;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.randomAutoscalingPolicy;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.sameInstance;
public class TransportPutAutoscalingPolicyActionIT extends AutoscalingIntegTestCase {
public void testAddPolicy() {
final AutoscalingPolicy policy = putRandomAutoscalingPolicy();
final ClusterState state = client().admin().cluster().prepareState().get().getState();
final AutoscalingMetadata metadata = state.metadata().custom(AutoscalingMetadata.NAME);
assertNotNull(metadata);
assertThat(metadata.policies(), hasKey(policy.name()));
assertThat(metadata.policies().get(policy.name()).policy(), equalTo(policy));
}
public void testUpdatePolicy() {
final AutoscalingPolicy policy = putRandomAutoscalingPolicy();
final AutoscalingPolicy updatedPolicy = new AutoscalingPolicy(policy.name(), mutateAutoscalingDeciders(policy.deciders()));
putAutoscalingPolicy(updatedPolicy);
final ClusterState state = client().admin().cluster().prepareState().get().getState();
final AutoscalingMetadata metadata = state.metadata().custom(AutoscalingMetadata.NAME);
assertNotNull(metadata);
assertThat(metadata.policies(), hasKey(policy.name()));
assertThat(metadata.policies().get(policy.name()).policy(), equalTo(updatedPolicy));
}
public void testNoOpPolicy() {
final AutoscalingPolicy policy = putRandomAutoscalingPolicy();
final ClusterState beforeState = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName()).state();
putAutoscalingPolicy(policy);
final ClusterState afterState = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName()).state();
assertThat(
beforeState.metadata().custom(AutoscalingMetadata.NAME),
sameInstance(afterState.metadata().custom(AutoscalingMetadata.NAME))
);
}
private AutoscalingPolicy putRandomAutoscalingPolicy() {
final AutoscalingPolicy policy = randomAutoscalingPolicy();
putAutoscalingPolicy(policy);
return policy;
}
private void putAutoscalingPolicy(final AutoscalingPolicy policy) {
final PutAutoscalingPolicyAction.Request request = new PutAutoscalingPolicyAction.Request(policy);
assertAcked(client().execute(PutAutoscalingPolicyAction.INSTANCE, request).actionGet());
}
}

View File

@ -0,0 +1,164 @@
/*
* 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.apache.logging.log4j.Logger;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.coordination.NoMasterBlockService;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
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 java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class TransportPutAutoscalingPolicyActionTests extends AutoscalingTestCase {
public void testWriteBlock() {
final TransportPutAutoscalingPolicyAction action = new TransportPutAutoscalingPolicyAction(
mock(TransportService.class),
mock(ClusterService.class),
mock(ThreadPool.class),
mock(ActionFilters.class),
mock(IndexNameExpressionResolver.class)
);
final ClusterBlocks blocks = ClusterBlocks.builder()
.addGlobalBlock(
randomFrom(
Metadata.CLUSTER_READ_ONLY_BLOCK,
Metadata.CLUSTER_READ_ONLY_ALLOW_DELETE_BLOCK,
NoMasterBlockService.NO_MASTER_BLOCK_WRITES
)
)
.build();
final ClusterState state = ClusterState.builder(new ClusterName(randomAlphaOfLength(8))).blocks(blocks).build();
final ClusterBlockException e = action.checkBlock(new PutAutoscalingPolicyAction.Request(randomAutoscalingPolicy()), state);
assertThat(e, not(nullValue()));
}
public void testNoWriteBlock() {
final TransportPutAutoscalingPolicyAction action = new TransportPutAutoscalingPolicyAction(
mock(TransportService.class),
mock(ClusterService.class),
mock(ThreadPool.class),
mock(ActionFilters.class),
mock(IndexNameExpressionResolver.class)
);
final ClusterBlocks blocks = ClusterBlocks.builder().build();
final ClusterState state = ClusterState.builder(new ClusterName(randomAlphaOfLength(8))).blocks(blocks).build();
final ClusterBlockException e = action.checkBlock(new PutAutoscalingPolicyAction.Request(randomAutoscalingPolicy()), state);
assertThat(e, nullValue());
}
public void testAddPolicy() {
final ClusterState currentState;
{
final ClusterState.Builder builder = ClusterState.builder(new ClusterName(randomAlphaOfLength(8)));
if (randomBoolean()) {
builder.metadata(Metadata.builder().putCustom(AutoscalingMetadata.NAME, randomAutoscalingMetadata()));
}
currentState = builder.build();
}
// put an entirely new policy
final AutoscalingPolicy policy = randomAutoscalingPolicy();
final Logger mockLogger = mock(Logger.class);
final ClusterState state = TransportPutAutoscalingPolicyAction.putAutoscalingPolicy(currentState, policy, mockLogger);
// ensure the new policy is in the updated cluster state
final AutoscalingMetadata metadata = state.metadata().custom(AutoscalingMetadata.NAME);
assertNotNull(metadata);
assertThat(metadata.policies(), hasKey(policy.name()));
assertThat(metadata.policies().get(policy.name()).policy(), equalTo(policy));
verify(mockLogger).info("adding autoscaling policy [{}]", policy.name());
verifyNoMoreInteractions(mockLogger);
// ensure that existing policies were preserved
final AutoscalingMetadata currentMetadata = currentState.metadata().custom(AutoscalingMetadata.NAME);
if (currentMetadata != null) {
for (final Map.Entry<String, AutoscalingPolicyMetadata> entry : currentMetadata.policies().entrySet()) {
assertThat(metadata.policies(), hasKey(entry.getKey()));
assertThat(metadata.policies().get(entry.getKey()).policy(), equalTo(entry.getValue().policy()));
}
}
}
public void testUpdatePolicy() {
final ClusterState currentState;
{
final ClusterState.Builder builder = ClusterState.builder(new ClusterName(randomAlphaOfLength(8)));
builder.metadata(
Metadata.builder().putCustom(AutoscalingMetadata.NAME, randomAutoscalingMetadataOfPolicyCount(randomIntBetween(1, 8)))
);
currentState = builder.build();
}
final AutoscalingMetadata currentMetadata = currentState.metadata().custom(AutoscalingMetadata.NAME);
final String name = randomFrom(currentMetadata.policies().keySet());
// add to the existing deciders, to ensure the policy has changed
final AutoscalingPolicy policy = new AutoscalingPolicy(
name,
mutateAutoscalingDeciders(currentMetadata.policies().get(name).policy().deciders())
);
final Logger mockLogger = mock(Logger.class);
final ClusterState state = TransportPutAutoscalingPolicyAction.putAutoscalingPolicy(currentState, policy, mockLogger);
// ensure the updated policy is in the updated cluster state
final AutoscalingMetadata metadata = state.metadata().custom(AutoscalingMetadata.NAME);
assertNotNull(metadata);
assertThat(metadata.policies(), hasKey(policy.name()));
assertThat(metadata.policies().get(policy.name()).policy(), equalTo(policy));
verify(mockLogger).info("updating autoscaling policy [{}]", policy.name());
verifyNoMoreInteractions(mockLogger);
// ensure that existing policies were otherwise preserved
for (final Map.Entry<String, AutoscalingPolicyMetadata> entry : currentMetadata.policies().entrySet()) {
if (entry.getKey().equals(name)) {
continue;
}
assertThat(metadata.policies(), hasKey(entry.getKey()));
assertThat(metadata.policies().get(entry.getKey()).policy(), equalTo(entry.getValue().policy()));
}
}
public void testNoOpUpdatePolicy() {
final ClusterState currentState;
{
final ClusterState.Builder builder = ClusterState.builder(new ClusterName(randomAlphaOfLength(8)));
builder.metadata(
Metadata.builder().putCustom(AutoscalingMetadata.NAME, randomAutoscalingMetadataOfPolicyCount(randomIntBetween(1, 8)))
);
currentState = builder.build();
}
// randomly put an existing policy
final AutoscalingMetadata currentMetadata = currentState.metadata().custom(AutoscalingMetadata.NAME);
final AutoscalingPolicy policy = randomFrom(currentMetadata.policies().values()).policy();
final Logger mockLogger = mock(Logger.class);
final ClusterState state = TransportPutAutoscalingPolicyAction.putAutoscalingPolicy(currentState, policy, mockLogger);
assertThat(state, sameInstance(currentState));
verify(mockLogger).info("skipping updating autoscaling policy [{}] due to no change in policy", policy.name());
verifyNoMoreInteractions(mockLogger);
}
}

View File

@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.xpack.autoscaling.AutoscalingTestCase;
import java.io.IOException;

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.Writeable;

View File

@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.autoscaling.AutoscalingTestCase;
public class AutoscalingDecisionWireSerializingTests extends AbstractWireSerializingTestCase<AutoscalingDecision> {

View File

@ -4,7 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.xpack.autoscaling.AutoscalingTestCase;
import java.util.Collections;

View File

@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.autoscaling;
package org.elasticsearch.xpack.autoscaling.decision;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.autoscaling.AutoscalingTestCase;
public class AutoscalingDecisionsWireSerializingTests extends AbstractWireSerializingTestCase<AutoscalingDecisions> {

View File

@ -0,0 +1,66 @@
/*
* 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.policy;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractDiffableSerializationTestCase;
import org.elasticsearch.xpack.autoscaling.Autoscaling;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.mutateAutoscalingPolicy;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.randomAutoscalingPolicyOfName;
public class AutoscalingPolicyMetadataDiffableSerializationTests extends AbstractDiffableSerializationTestCase<AutoscalingPolicyMetadata> {
private final String name = randomAlphaOfLength(8);
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(new Autoscaling(Settings.EMPTY).getNamedWriteables());
}
@Override
protected NamedXContentRegistry xContentRegistry() {
return new NamedXContentRegistry(new Autoscaling(Settings.EMPTY).getNamedXContent());
}
@Override
protected AutoscalingPolicyMetadata doParseInstance(final XContentParser parser) {
return AutoscalingPolicyMetadata.parse(parser, name);
}
@Override
protected Writeable.Reader<AutoscalingPolicyMetadata> instanceReader() {
return AutoscalingPolicyMetadata::new;
}
@Override
protected AutoscalingPolicyMetadata createTestInstance() {
return new AutoscalingPolicyMetadata(randomAutoscalingPolicyOfName(name));
}
@Override
protected AutoscalingPolicyMetadata makeTestChanges(final AutoscalingPolicyMetadata testInstance) {
return mutateInstance(testInstance);
}
@Override
protected AutoscalingPolicyMetadata mutateInstance(final AutoscalingPolicyMetadata instance) {
return new AutoscalingPolicyMetadata(mutateAutoscalingPolicy(instance.policy()));
}
@Override
protected Writeable.Reader<Diff<AutoscalingPolicyMetadata>> diffReader() {
return in -> AbstractDiffable.readDiffFrom(AutoscalingPolicyMetadata::new, in);
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.policy;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractSerializingTestCase;
import org.elasticsearch.xpack.autoscaling.Autoscaling;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.mutateAutoscalingPolicy;
import static org.elasticsearch.xpack.autoscaling.AutoscalingTestCase.randomAutoscalingPolicyOfName;
public class AutoscalingPolicySerializingTests extends AbstractSerializingTestCase<AutoscalingPolicy> {
private final String name = randomAlphaOfLength(8);
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(new Autoscaling(Settings.EMPTY).getNamedWriteables());
}
@Override
protected NamedXContentRegistry xContentRegistry() {
return new NamedXContentRegistry(new Autoscaling(Settings.EMPTY).getNamedXContent());
}
@Override
protected AutoscalingPolicy doParseInstance(final XContentParser parser) {
return AutoscalingPolicy.parse(parser, name);
}
@Override
protected Writeable.Reader<AutoscalingPolicy> instanceReader() {
return AutoscalingPolicy::new;
}
@Override
protected AutoscalingPolicy createTestInstance() {
return randomAutoscalingPolicyOfName(name);
}
@Override
protected AutoscalingPolicy mutateInstance(final AutoscalingPolicy instance) {
return mutateAutoscalingPolicy(instance);
}
}

View File

@ -102,7 +102,7 @@ public class LocalStateCompositeXPackPlugin extends XPackPlugin implements Scrip
private LicenseService licenseService;
protected List<Plugin> plugins = new ArrayList<>();
public LocalStateCompositeXPackPlugin(final Settings settings, final Path configPath) throws Exception {
public LocalStateCompositeXPackPlugin(final Settings settings, final Path configPath) {
super(settings, configPath);
}

View File

@ -0,0 +1,28 @@
{
"autoscaling.put_autoscaling_policy":{
"documentation":{
"url":"https://www.elastic.co/guide/en/elasticsearch/reference/current/autoscaling-put-autoscaling-policy.html"
},
"stability":"experimental",
"url":{
"paths":[
{
"path":"/_autoscaling/policy/{name}",
"methods":[
"PUT"
],
"parts":{
"name":{
"type":"string",
"description":"the name of the autoscaling policy"
}
}
}
]
},
"body":{
"description":"the specification of the autoscaling policy",
"required":true
}
}
}