copy LifecyclePolicy to protocol.xpack (#32915)

This is the final PR for copying over the necessary components for
clients to parse/render LifecyclePolicy. Changes include:

- move of named-x-content server objects away from client
- move validation into the client copy of LifecyclePolicy
- move LifecycleAction into an interface with `getName`
This commit is contained in:
Tal Levy 2018-08-20 08:32:22 -07:00 committed by GitHub
parent 3736097e19
commit 5ce082cd0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 711 additions and 44 deletions

View File

@ -440,20 +440,7 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl
RollupJobStatus::fromXContent),
new NamedXContentRegistry.Entry(PersistentTaskState.class, new ParseField(RollupJobStatus.NAME),
RollupJobStatus::fromXContent),
// ILM - Custom Metadata
new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(IndexLifecycleMetadata.TYPE),
parser -> IndexLifecycleMetadata.PARSER.parse(parser, null)),
// ILM - Lifecycle Types
new NamedXContentRegistry.Entry(LifecycleType.class, new ParseField(TimeseriesLifecycleType.TYPE),
(p, c) -> TimeseriesLifecycleType.INSTANCE),
// ILM - Lifecycle Actions
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(AllocateAction.NAME), AllocateAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ForceMergeAction.NAME), ForceMergeAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ReadOnlyAction.NAME), ReadOnlyAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(RolloverAction.NAME), RolloverAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ShrinkAction.NAME), ShrinkAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(DeleteAction.NAME), DeleteAction::parse),
// ILM - Client - Lifecycle Actions
// ILM
new NamedXContentRegistry.Entry(org.elasticsearch.protocol.xpack.indexlifecycle.LifecycleAction.class,
new ParseField(org.elasticsearch.protocol.xpack.indexlifecycle.AllocateAction.NAME),
org.elasticsearch.protocol.xpack.indexlifecycle.AllocateAction::parse),

View File

@ -34,10 +34,9 @@ import java.util.stream.Collectors;
/**
* Represents the lifecycle of an index from creation to deletion. A
* {@link LifecyclePolicy} is made up of a set of {@link Phase}s which it will
* move through. Soon we will constrain the phases using some kinda of lifecycle
* type which will allow only particular {@link Phase}s to be defined, will
* dictate the order in which the {@link Phase}s are executed and will define
* which {@link LifecycleAction}s are allowed in each phase.
* move through. Policies are constrained by a {@link LifecycleType} which governs which
* {@link Phase}s and {@link LifecycleAction}s are allowed to be defined and in which order
* they are executed.
*/
public class LifecyclePolicy extends AbstractDiffable<LifecyclePolicy>
implements ToXContentObject, Diffable<LifecyclePolicy> {
@ -58,9 +57,9 @@ public class LifecyclePolicy extends AbstractDiffable<LifecyclePolicy>
}, PHASES_FIELD);
}
protected final String name;
protected final LifecycleType type;
protected final Map<String, Phase> phases;
private final String name;
private final LifecycleType type;
private final Map<String, Phase> phases;
/**
* @param name
@ -97,6 +96,7 @@ public class LifecyclePolicy extends AbstractDiffable<LifecyclePolicy>
this.type = type;
this.type.validate(phases.values());
}
public static LifecyclePolicy parse(XContentParser parser, String name) {
return PARSER.apply(parser, name);
}

View File

@ -51,9 +51,9 @@ public class Phase implements ToXContentObject, Writeable {
return PARSER.apply(parser, name);
}
private String name;
private Map<String, LifecycleAction> actions;
private TimeValue after;
private final String name;
private final Map<String, LifecycleAction> actions;
private final TimeValue after;
/**
* @param name
@ -133,12 +133,12 @@ public class Phase implements ToXContentObject, Writeable {
builder.endObject();
return builder;
}
@Override
public int hashCode() {
return Objects.hash(name, after, actions);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {

View File

@ -15,7 +15,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -46,6 +45,14 @@ public class TimeseriesLifecycleType implements LifecycleType {
static final Set<String> VALID_DELETE_ACTIONS = Sets.newHashSet(ORDERED_VALID_DELETE_ACTIONS);
private static final Phase EMPTY_WARM_PHASE = new Phase("warm", TimeValue.ZERO,
Collections.singletonMap("readonly", ReadOnlyAction.INSTANCE));
private static Map<String, Set<String>> ALLOWED_ACTIONS = new HashMap<>();
static {
ALLOWED_ACTIONS.put("hot", VALID_HOT_ACTIONS);
ALLOWED_ACTIONS.put("warm", VALID_WARM_ACTIONS);
ALLOWED_ACTIONS.put("cold", VALID_COLD_ACTIONS);
ALLOWED_ACTIONS.put("delete", VALID_DELETE_ACTIONS);
}
private TimeseriesLifecycleType() {
}
@ -182,18 +189,12 @@ public class TimeseriesLifecycleType implements LifecycleType {
@Override
public void validate(Collection<Phase> phases) {
Set<String> allowedPhases = new HashSet<>(VALID_PHASES);
Map<String, Set<String>> allowedActions = new HashMap<>(allowedPhases.size());
allowedActions.put("hot", VALID_HOT_ACTIONS);
allowedActions.put("warm", VALID_WARM_ACTIONS);
allowedActions.put("cold", VALID_COLD_ACTIONS);
allowedActions.put("delete", VALID_DELETE_ACTIONS);
phases.forEach(phase -> {
if (allowedPhases.contains(phase.getName()) == false) {
if (ALLOWED_ACTIONS.containsKey(phase.getName()) == false) {
throw new IllegalArgumentException("Timeseries lifecycle does not support phase [" + phase.getName() + "]");
}
phase.getActions().forEach((actionName, action) -> {
if (allowedActions.get(phase.getName()).contains(actionName) == false) {
if (ALLOWED_ACTIONS.get(phase.getName()).contains(actionName) == false) {
throw new IllegalArgumentException("invalid action [" + actionName + "] " +
"defined in phase [" + phase.getName() +"]");
}

View File

@ -10,8 +10,10 @@ import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.client.Client;
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.inject.Module;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
@ -32,8 +34,17 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.indexlifecycle.AllocateAction;
import org.elasticsearch.xpack.core.indexlifecycle.DeleteAction;
import org.elasticsearch.xpack.core.indexlifecycle.ForceMergeAction;
import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleType;
import org.elasticsearch.xpack.core.indexlifecycle.ReadOnlyAction;
import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction;
import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction;
import org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType;
import org.elasticsearch.xpack.core.indexlifecycle.action.DeleteLifecycleAction;
import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction;
import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction;
@ -146,7 +157,21 @@ public class IndexLifecycle extends Plugin implements ActionPlugin {
@Override
public List<org.elasticsearch.common.xcontent.NamedXContentRegistry.Entry> getNamedXContent() {
return Arrays.asList();
return Arrays.asList(
// Custom Metadata
new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(IndexLifecycleMetadata.TYPE),
parser -> IndexLifecycleMetadata.PARSER.parse(parser, null)),
// Lifecycle Types
new NamedXContentRegistry.Entry(LifecycleType.class, new ParseField(TimeseriesLifecycleType.TYPE),
(p, c) -> TimeseriesLifecycleType.INSTANCE),
// Lifecycle Actions
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(AllocateAction.NAME), AllocateAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ForceMergeAction.NAME), ForceMergeAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ReadOnlyAction.NAME), ReadOnlyAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(RolloverAction.NAME), RolloverAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ShrinkAction.NAME), ShrinkAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(DeleteAction.NAME), DeleteAction::parse)
);
}
@Override

View File

@ -31,7 +31,7 @@ import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class AllocateAction extends LifecycleAction implements ToXContentObject {
public class AllocateAction implements LifecycleAction, ToXContentObject {
public static final String NAME = "allocate";
static final ParseField NUMBER_OF_REPLICAS_FIELD = new ParseField("number_of_replicas");
@ -102,6 +102,11 @@ public class AllocateAction extends LifecycleAction implements ToXContentObject
return require;
}
@Override
public String getName() {
return NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();

View File

@ -27,7 +27,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
public class DeleteAction extends LifecycleAction implements ToXContentObject {
public class DeleteAction implements LifecycleAction, ToXContentObject {
public static final String NAME = "delete";
private static final ObjectParser<DeleteAction, Void> PARSER = new ObjectParser<>(NAME, DeleteAction::new);
@ -46,6 +46,11 @@ public class DeleteAction extends LifecycleAction implements ToXContentObject {
return builder;
}
@Override
public String getName() {
return NAME;
}
@Override
public int hashCode() {
return 1;

View File

@ -28,7 +28,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Objects;
public class ForceMergeAction extends LifecycleAction implements ToXContentObject {
public class ForceMergeAction implements LifecycleAction, ToXContentObject {
public static final String NAME = "forcemerge";
private static final ParseField MAX_NUM_SEGMENTS_FIELD = new ParseField("max_num_segments");
@ -60,6 +60,11 @@ public class ForceMergeAction extends LifecycleAction implements ToXContentObjec
return maxNumSegments;
}
@Override
public String getName() {
return NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -19,7 +19,12 @@
package org.elasticsearch.protocol.xpack.indexlifecycle;
/**
* Marker interface for index lifecycle management actions
* interface for index lifecycle management actions
*/
public class LifecycleAction {
public interface LifecycleAction {
/**
* @return the name of this action
*/
String getName();
}

View File

@ -0,0 +1,145 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.indexlifecycle;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.util.set.Sets;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Represents the lifecycle of an index from creation to deletion. A
* {@link LifecyclePolicy} is made up of a set of {@link Phase}s which it will
* move through.
*/
public class LifecyclePolicy implements ToXContentObject {
static final ParseField PHASES_FIELD = new ParseField("phases");
@SuppressWarnings("unchecked")
public static ConstructingObjectParser<LifecyclePolicy, String> PARSER = new ConstructingObjectParser<>("lifecycle_policy", false,
(a, name) -> {
List<Phase> phases = (List<Phase>) a[0];
Map<String, Phase> phaseMap = phases.stream().collect(Collectors.toMap(Phase::getName, Function.identity()));
return new LifecyclePolicy(name, phaseMap);
});
private static Map<String, Set<String>> ALLOWED_ACTIONS = new HashMap<>();
static {
PARSER.declareNamedObjects(ConstructingObjectParser.constructorArg(), (p, c, n) -> Phase.parse(p, n), v -> {
throw new IllegalArgumentException("ordered " + PHASES_FIELD.getPreferredName() + " are not supported");
}, PHASES_FIELD);
ALLOWED_ACTIONS.put("hot", Sets.newHashSet(RolloverAction.NAME));
ALLOWED_ACTIONS.put("warm", Sets.newHashSet(AllocateAction.NAME, ForceMergeAction.NAME, ReadOnlyAction.NAME, ShrinkAction.NAME));
ALLOWED_ACTIONS.put("cold", Sets.newHashSet(AllocateAction.NAME));
ALLOWED_ACTIONS.put("delete", Sets.newHashSet(DeleteAction.NAME));
}
private final String name;
private final Map<String, Phase> phases;
/**
* @param name
* the name of this {@link LifecyclePolicy}
* @param phases
* a {@link Map} of {@link Phase}s which make up this
* {@link LifecyclePolicy}.
*/
public LifecyclePolicy(String name, Map<String, Phase> phases) {
phases.values().forEach(phase -> {
if (ALLOWED_ACTIONS.containsKey(phase.getName()) == false) {
throw new IllegalArgumentException("Lifecycle does not support phase [" + phase.getName() + "]");
}
phase.getActions().forEach((actionName, action) -> {
if (ALLOWED_ACTIONS.get(phase.getName()).contains(actionName) == false) {
throw new IllegalArgumentException("invalid action [" + actionName + "] " +
"defined in phase [" + phase.getName() +"]");
}
});
});
this.name = name;
this.phases = phases;
}
public static LifecyclePolicy parse(XContentParser parser, String name) {
return PARSER.apply(parser, name);
}
/**
* @return the name of this {@link LifecyclePolicy}
*/
public String getName() {
return name;
}
/**
* @return the {@link Phase}s for this {@link LifecyclePolicy} in the order
* in which they will be executed.
*/
public Map<String, Phase> getPhases() {
return phases;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.startObject(PHASES_FIELD.getPreferredName());
for (Phase phase : phases.values()) {
builder.field(phase.getName(), phase);
}
builder.endObject();
builder.endObject();
return builder;
}
@Override
public int hashCode() {
return Objects.hash(name, phases);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
LifecyclePolicy other = (LifecyclePolicy) obj;
return Objects.equals(name, other.name) &&
Objects.equals(phases, other.phases);
}
@Override
public String toString() {
return Strings.toString(this, true, true);
}
}

View File

@ -0,0 +1,143 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.indexlifecycle;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
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.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Represents set of {@link LifecycleAction}s which should be executed at a
* particular point in the lifecycle of an index.
*/
public class Phase implements ToXContentObject {
static final ParseField AFTER_FIELD = new ParseField("after");
static final ParseField ACTIONS_FIELD = new ParseField("actions");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Phase, String> PARSER = new ConstructingObjectParser<>("phase", false,
(a, name) -> new Phase(name, (TimeValue) a[0], ((List<LifecycleAction>) a[1]).stream()
.collect(Collectors.toMap(LifecycleAction::getName, Function.identity()))));
static {
PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
(p, c) -> TimeValue.parseTimeValue(p.text(), AFTER_FIELD.getPreferredName()), AFTER_FIELD, ValueType.VALUE);
PARSER.declareNamedObjects(ConstructingObjectParser.constructorArg(),
(p, c, n) -> p.namedObject(LifecycleAction.class, n, null), v -> {
throw new IllegalArgumentException("ordered " + ACTIONS_FIELD.getPreferredName() + " are not supported");
}, ACTIONS_FIELD);
}
public static Phase parse(XContentParser parser, String name) {
return PARSER.apply(parser, name);
}
private final String name;
private final Map<String, LifecycleAction> actions;
private final TimeValue after;
/**
* @param name
* the name of this {@link Phase}.
* @param after
* the age of the index when the index should move to this
* {@link Phase}.
* @param actions
* a {@link Map} of the {@link LifecycleAction}s to run when
* during this {@link Phase}. The keys in this map are the associated
* action names.
*/
public Phase(String name, TimeValue after, Map<String, LifecycleAction> actions) {
this.name = name;
if (after == null) {
this.after = TimeValue.ZERO;
} else {
this.after = after;
}
this.actions = actions;
}
/**
* @return the age of the index when the index should move to this
* {@link Phase}.
*/
public TimeValue getAfter() {
return after;
}
/**
* @return the name of this {@link Phase}
*/
public String getName() {
return name;
}
/**
* @return a {@link Map} of the {@link LifecycleAction}s to run when during
* this {@link Phase}.
*/
public Map<String, LifecycleAction> getActions() {
return actions;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(AFTER_FIELD.getPreferredName(), after.seconds() + "s"); // Need a better way to get a parsable format out here
builder.field(ACTIONS_FIELD.getPreferredName(), actions);
builder.endObject();
return builder;
}
@Override
public int hashCode() {
return Objects.hash(name, after, actions);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
Phase other = (Phase) obj;
return Objects.equals(name, other.name) &&
Objects.equals(after, other.after) &&
Objects.equals(actions, other.actions);
}
@Override
public String toString() {
return Strings.toString(this, true, true);
}
}

View File

@ -26,7 +26,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
public class ReadOnlyAction extends LifecycleAction implements ToXContentObject {
public class ReadOnlyAction implements LifecycleAction, ToXContentObject {
public static final String NAME = "readonly";
private static final ObjectParser<ReadOnlyAction, Void> PARSER = new ObjectParser<>(NAME, false, ReadOnlyAction::new);
@ -38,6 +38,11 @@ public class ReadOnlyAction extends LifecycleAction implements ToXContentObject
public ReadOnlyAction() {
}
@Override
public String getName() {
return NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -32,7 +32,7 @@ import java.io.IOException;
import java.util.Objects;
public class RolloverAction extends LifecycleAction implements ToXContentObject {
public class RolloverAction implements LifecycleAction, ToXContentObject {
public static final String NAME = "rollover";
private static final ParseField MAX_SIZE_FIELD = new ParseField("max_size");
private static final ParseField MAX_DOCS_FIELD = new ParseField("max_docs");
@ -76,6 +76,11 @@ public class RolloverAction extends LifecycleAction implements ToXContentObject
return maxDocs;
}
@Override
public String getName() {
return NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -28,7 +28,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Objects;
public class ShrinkAction extends LifecycleAction implements ToXContentObject {
public class ShrinkAction implements LifecycleAction, ToXContentObject {
public static final String NAME = "shrink";
private static final ParseField NUMBER_OF_SHARDS_FIELD = new ParseField("number_of_shards");
@ -56,6 +56,11 @@ public class ShrinkAction extends LifecycleAction implements ToXContentObject {
return numberOfShards;
}
@Override
public String getName() {
return NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -29,6 +29,10 @@ public class AllocateActionTests extends AbstractXContentTestCase<AllocateAction
@Override
protected AllocateAction createTestInstance() {
return randomInstance();
}
static AllocateAction randomInstance() {
boolean hasAtLeastOneMap = false;
Map<String, String> includes;
if (randomBoolean()) {

View File

@ -44,6 +44,10 @@ public class ForceMergeActionTests extends AbstractXContentTestCase<ForceMergeAc
@Override
protected ForceMergeAction createTestInstance() {
return randomInstance();
}
static ForceMergeAction randomInstance() {
return new ForceMergeAction(randomIntBetween(1, 100));
}

View File

@ -0,0 +1,239 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.indexlifecycle;
import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
public class LifecyclePolicyTests extends AbstractXContentTestCase<LifecyclePolicy> {
private static final Set<String> VALID_HOT_ACTIONS = Sets.newHashSet(RolloverAction.NAME);
private static final Set<String> VALID_WARM_ACTIONS = Sets.newHashSet(AllocateAction.NAME, ForceMergeAction.NAME,
ReadOnlyAction.NAME, ShrinkAction.NAME);
private static final Set<String> VALID_COLD_ACTIONS = Sets.newHashSet(AllocateAction.NAME);
private static final Set<String> VALID_DELETE_ACTIONS = Sets.newHashSet(DeleteAction.NAME);
private String lifecycleName;
@Override
protected LifecyclePolicy doParseInstance(XContentParser parser) {
return LifecyclePolicy.parse(parser, lifecycleName);
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
@Override
protected NamedXContentRegistry xContentRegistry() {
List<NamedXContentRegistry.Entry> entries = new ArrayList<>(ClusterModule.getNamedXWriteables());
entries.addAll(Arrays.asList(
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(AllocateAction.NAME), AllocateAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(DeleteAction.NAME), DeleteAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ForceMergeAction.NAME), ForceMergeAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ReadOnlyAction.NAME), ReadOnlyAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(RolloverAction.NAME), RolloverAction::parse),
new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ShrinkAction.NAME), ShrinkAction::parse)
));
return new NamedXContentRegistry(entries);
}
@Override
protected LifecyclePolicy createTestInstance() {
lifecycleName = randomAlphaOfLength(5);
List<String> phaseNames = randomSubsetOf(Arrays.asList("hot", "warm", "cold", "delete"));
Map<String, Phase> phases = new HashMap<>(phaseNames.size());
Function<String, Set<String>> validActions = (phase) -> {
switch (phase) {
case "hot":
return VALID_HOT_ACTIONS;
case "warm":
return VALID_WARM_ACTIONS;
case "cold":
return VALID_COLD_ACTIONS;
case "delete":
return VALID_DELETE_ACTIONS;
default:
throw new IllegalArgumentException("invalid phase [" + phase + "]");
}};
Function<String, LifecycleAction> randomAction = (action) -> {
switch (action) {
case AllocateAction.NAME:
return AllocateActionTests.randomInstance();
case DeleteAction.NAME:
return new DeleteAction();
case ForceMergeAction.NAME:
return ForceMergeActionTests.randomInstance();
case ReadOnlyAction.NAME:
return new ReadOnlyAction();
case RolloverAction.NAME:
return RolloverActionTests.randomInstance();
case ShrinkAction.NAME:
return ShrinkActionTests.randomInstance();
default:
throw new IllegalArgumentException("invalid action [" + action + "]");
}};
for (String phase : phaseNames) {
TimeValue after = TimeValue.parseTimeValue(randomTimeValue(0, 1000000000, "s", "m", "h", "d"), "test_after");
Map<String, LifecycleAction> actions = new HashMap<>();
List<String> actionNames = randomSubsetOf(validActions.apply(phase));
for (String action : actionNames) {
actions.put(action, randomAction.apply(action));
}
phases.put(phase, new Phase(phase, after, actions));
}
return new LifecyclePolicy(lifecycleName, phases);
}
public void testValidatePhases() {
boolean invalid = randomBoolean();
String phaseName = randomFrom("hot", "warm", "cold", "delete");
if (invalid) {
phaseName += randomAlphaOfLength(5);
}
Map<String, Phase> phases = Collections.singletonMap(phaseName,
new Phase(phaseName, TimeValue.ZERO, Collections.emptyMap()));
if (invalid) {
Exception e = expectThrows(IllegalArgumentException.class, () -> new LifecyclePolicy(lifecycleName, phases));
assertThat(e.getMessage(), equalTo("Lifecycle does not support phase [" + phaseName + "]"));
} else {
new LifecyclePolicy(lifecycleName, phases);
}
}
public void testValidateHotPhase() {
LifecycleAction invalidAction = null;
Map<String, LifecycleAction> actions = randomSubsetOf(VALID_HOT_ACTIONS)
.stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getName, Function.identity()));
if (randomBoolean()) {
invalidAction = getTestAction(randomFrom("allocate", "forcemerge", "delete", "shrink"));
actions.put(invalidAction.getName(), invalidAction);
}
Map<String, Phase> hotPhase = Collections.singletonMap("hot",
new Phase("hot", TimeValue.ZERO, actions));
if (invalidAction != null) {
Exception e = expectThrows(IllegalArgumentException.class,
() -> new LifecyclePolicy(lifecycleName, hotPhase));
assertThat(e.getMessage(),
equalTo("invalid action [" + invalidAction.getName() + "] defined in phase [hot]"));
} else {
new LifecyclePolicy(lifecycleName, hotPhase);
}
}
public void testValidateWarmPhase() {
LifecycleAction invalidAction = null;
Map<String, LifecycleAction> actions = randomSubsetOf(VALID_WARM_ACTIONS)
.stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getName, Function.identity()));
if (randomBoolean()) {
invalidAction = getTestAction(randomFrom("rollover", "delete"));
actions.put(invalidAction.getName(), invalidAction);
}
Map<String, Phase> warmPhase = Collections.singletonMap("warm",
new Phase("warm", TimeValue.ZERO, actions));
if (invalidAction != null) {
Exception e = expectThrows(IllegalArgumentException.class,
() -> new LifecyclePolicy(lifecycleName, warmPhase));
assertThat(e.getMessage(),
equalTo("invalid action [" + invalidAction.getName() + "] defined in phase [warm]"));
} else {
new LifecyclePolicy(lifecycleName, warmPhase);
}
}
public void testValidateColdPhase() {
LifecycleAction invalidAction = null;
Map<String, LifecycleAction> actions = randomSubsetOf(VALID_COLD_ACTIONS)
.stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getName, Function.identity()));
if (randomBoolean()) {
invalidAction = getTestAction(randomFrom("rollover", "delete", "forcemerge", "shrink"));
actions.put(invalidAction.getName(), invalidAction);
}
Map<String, Phase> coldPhase = Collections.singletonMap("cold",
new Phase("cold", TimeValue.ZERO, actions));
if (invalidAction != null) {
Exception e = expectThrows(IllegalArgumentException.class,
() -> new LifecyclePolicy(lifecycleName, coldPhase));
assertThat(e.getMessage(),
equalTo("invalid action [" + invalidAction.getName() + "] defined in phase [cold]"));
} else {
new LifecyclePolicy(lifecycleName, coldPhase);
}
}
public void testValidateDeletePhase() {
LifecycleAction invalidAction = null;
Map<String, LifecycleAction> actions = VALID_DELETE_ACTIONS
.stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getName, Function.identity()));
if (randomBoolean()) {
invalidAction = getTestAction(randomFrom("allocate", "rollover", "forcemerge", "shrink"));
actions.put(invalidAction.getName(), invalidAction);
}
Map<String, Phase> deletePhase = Collections.singletonMap("delete",
new Phase("delete", TimeValue.ZERO, actions));
if (invalidAction != null) {
Exception e = expectThrows(IllegalArgumentException.class,
() -> new LifecyclePolicy(lifecycleName, deletePhase));
assertThat(e.getMessage(),
equalTo("invalid action [" + invalidAction.getName() + "] defined in phase [delete]"));
} else {
new LifecyclePolicy(lifecycleName, deletePhase);
}
}
private LifecycleAction getTestAction(String actionName) {
switch (actionName) {
case AllocateAction.NAME:
return AllocateActionTests.randomInstance();
case DeleteAction.NAME:
return new DeleteAction();
case ForceMergeAction.NAME:
return ForceMergeActionTests.randomInstance();
case ReadOnlyAction.NAME:
return new ReadOnlyAction();
case RolloverAction.NAME:
return RolloverActionTests.randomInstance();
case ShrinkAction.NAME:
return ShrinkActionTests.randomInstance();
default:
throw new IllegalArgumentException("unsupported phase action [" + actionName + "]");
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.indexlifecycle;
import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class PhaseTests extends AbstractXContentTestCase<Phase> {
private String phaseName;
@Before
public void setup() {
phaseName = randomAlphaOfLength(20);
}
@Override
protected Phase createTestInstance() {
TimeValue after = null;
if (randomBoolean()) {
after = TimeValue.parseTimeValue(randomTimeValue(0, 1000000000, "s", "m", "h", "d"), "test_after");
}
Map<String, LifecycleAction> actions = Collections.emptyMap();
if (randomBoolean()) {
actions = Collections.singletonMap(DeleteAction.NAME, new DeleteAction());
}
return new Phase(phaseName, after, actions);
}
@Override
protected Phase doParseInstance(XContentParser parser) {
return Phase.parse(parser, phaseName);
}
@Override
protected NamedXContentRegistry xContentRegistry() {
List<NamedXContentRegistry.Entry> entries = new ArrayList<>(ClusterModule.getNamedXWriteables());
entries.add(new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(DeleteAction.NAME), DeleteAction::parse));
return new NamedXContentRegistry(entries);
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
public void testDefaultAfter() {
Phase phase = new Phase(randomAlphaOfLength(20), null, Collections.emptyMap());
assertEquals(TimeValue.ZERO, phase.getAfter());
}
}

View File

@ -38,6 +38,10 @@ public class RolloverActionTests extends AbstractXContentTestCase<RolloverAction
@Override
protected RolloverAction createTestInstance() {
return randomInstance();
}
static RolloverAction randomInstance() {
ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
ByteSizeValue maxSize = randomBoolean() ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
Long maxDocs = randomBoolean() ? null : randomNonNegativeLong();

View File

@ -34,6 +34,10 @@ public class ShrinkActionTests extends AbstractXContentTestCase<ShrinkAction> {
@Override
protected ShrinkAction createTestInstance() {
return randomInstance();
}
static ShrinkAction randomInstance() {
return new ShrinkAction(randomIntBetween(1, 100));
}