Skip to content

Commit

Permalink
[Transform] Allow specifying destination index aliases in the Transfo…
Browse files Browse the repository at this point in the history
…rm's dest config (#94943)
  • Loading branch information
przemekwitek committed Apr 17, 2023
1 parent 32c17d7 commit 2b70165
Show file tree
Hide file tree
Showing 41 changed files with 1,076 additions and 299 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/94943.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 94943
summary: Allow specifying destination index aliases in the Transform's `dest` config
area: Transform
type: enhancement
issues: []
19 changes: 19 additions & 0 deletions docs/reference/rest-api/common-parms.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,25 @@ mappings for the destination index are undesirable, use the
<<indices-create-index,Create index API>> prior to starting the {transform}.
end::dest-index[]

tag::dest-aliases[]
The aliases that the destination index for the {transform} should have.
Aliases are manipulated using the stored credentials of the transform, which means the secondary credentials supplied
at creation time (if both primary and secondary credentials are specified).

The destination index is added to the aliases regardless whether the destination
index was created by the transform or pre-created by the user.
end::dest-aliases[]

tag::dest-aliases-alias[]
The name of the alias.
end::dest-aliases-alias[]

tag::dest-aliases-move-on-creation[]
Whether or not the destination index should be the **only** index in this alias.
If `true`, all the other indices will be removed from this alias before adding the destination index to this alias.
Defaults to `false`.
end::dest-aliases-move-on-creation[]

tag::dest-pipeline[]
The unique identifier for an <<ingest,ingest pipeline>>.
end::dest-pipeline[]
Expand Down
20 changes: 20 additions & 0 deletions docs/reference/transform/apis/put-transform.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,26 @@ include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest]
(Required, string)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-index]
//Begin aliases
`aliases`:::
(Optional, array of objects)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-aliases]
+
.Properties of `aliases`
[%collapsible%open]
=====

`alias`::::
(Required, string)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-aliases-alias]

`move_on_creation`::::
(Optional, boolean)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-aliases-move-on-creation]

=====
//End aliases
`pipeline`:::
(Optional, string)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-pipeline]
Expand Down
20 changes: 20 additions & 0 deletions docs/reference/transform/apis/update-transform.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest]
(Required, string)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-index]
//Begin aliases
`aliases`:::
(Optional, array of objects)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-aliases]
+
.Properties of `aliases`
[%collapsible%open]
=====

`alias`::::
(Required, string)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-aliases-alias]

`move_on_creation`::::
(Optional, boolean)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-aliases-move-on-creation]

=====
//End aliases
`pipeline`:::
(Optional, string)
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=dest-pipeline]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class TransformMessages {
+ "Use force stop and then restart the transform once error is resolved.";

public static final String FAILED_TO_CREATE_DESTINATION_INDEX = "Could not create destination index [{0}] for transform [{1}]";
public static final String FAILED_TO_SET_UP_DESTINATION_ALIASES =
"Could not set up aliases for destination index [{0}] for transform [{1}]";
public static final String FAILED_TO_RELOAD_TRANSFORM_CONFIGURATION = "Failed to reload transform configuration for transform [{0}]";
public static final String FAILED_TO_LOAD_TRANSFORM_CONFIGURATION = "Failed to load transform configuration for transform [{0}]";
public static final String FAILED_TO_PARSE_TRANSFORM_CONFIGURATION = "Failed to parse transform configuration for transform [{0}]";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.transform.transforms;

import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper;

import java.io.IOException;
import java.util.Objects;

import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;

public class DestAlias implements Writeable, ToXContentObject {

public static final ParseField ALIAS = new ParseField("alias");
public static final ParseField MOVE_ON_CREATION = new ParseField("move_on_creation");

public static final ConstructingObjectParser<DestAlias, Void> STRICT_PARSER = createParser(false);
public static final ConstructingObjectParser<DestAlias, Void> LENIENT_PARSER = createParser(true);

private static ConstructingObjectParser<DestAlias, Void> createParser(boolean lenient) {
ConstructingObjectParser<DestAlias, Void> parser = new ConstructingObjectParser<>(
"data_frame_config_dest_alias",
lenient,
args -> new DestAlias((String) args[0], (Boolean) args[1])
);
parser.declareString(constructorArg(), ALIAS);
parser.declareBoolean(optionalConstructorArg(), MOVE_ON_CREATION);
return parser;
}

private final String alias;
private final boolean moveOnCreation;

public DestAlias(String alias, Boolean moveOnCreation) {
this.alias = ExceptionsHelper.requireNonNull(alias, ALIAS.getPreferredName());
this.moveOnCreation = moveOnCreation != null ? moveOnCreation : false;
}

public DestAlias(final StreamInput in) throws IOException {
alias = in.readString();
moveOnCreation = in.readBoolean();
}

public String getAlias() {
return alias;
}

public boolean isMoveOnCreation() {
return moveOnCreation;
}

public ActionRequestValidationException validate(ActionRequestValidationException validationException) {
if (alias.isEmpty()) {
validationException = addValidationError("dest.aliases.alias must not be empty", validationException);
}
return validationException;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(alias);
out.writeBoolean(moveOnCreation);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(ALIAS.getPreferredName(), alias);
builder.field(MOVE_ON_CREATION.getPreferredName(), moveOnCreation);
builder.endObject();
return builder;
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || other.getClass() != getClass()) {
return false;
}

DestAlias that = (DestAlias) other;
return Objects.equals(alias, that.alias) && moveOnCreation == that.moveOnCreation;
}

@Override
public int hashCode() {
return Objects.hash(alias, moveOnCreation);
}

public static DestAlias fromXContent(final XContentParser parser, boolean lenient) throws IOException {
return lenient ? LENIENT_PARSER.apply(parser, null) : STRICT_PARSER.apply(parser, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package org.elasticsearch.xpack.core.transform.transforms;

import org.elasticsearch.TransportVersion;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
Expand All @@ -21,6 +22,7 @@
import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

Expand All @@ -31,39 +33,53 @@
public class DestConfig implements Writeable, ToXContentObject {

public static final ParseField INDEX = new ParseField("index");
public static final ParseField ALIASES = new ParseField("aliases");
public static final ParseField PIPELINE = new ParseField("pipeline");

public static final ConstructingObjectParser<DestConfig, Void> STRICT_PARSER = createParser(false);
public static final ConstructingObjectParser<DestConfig, Void> LENIENT_PARSER = createParser(true);

@SuppressWarnings("unchecked")
private static ConstructingObjectParser<DestConfig, Void> createParser(boolean lenient) {
ConstructingObjectParser<DestConfig, Void> parser = new ConstructingObjectParser<>(
"data_frame_config_dest",
lenient,
args -> new DestConfig((String) args[0], (String) args[1])
args -> new DestConfig((String) args[0], (List<DestAlias>) args[1], (String) args[2])
);
parser.declareString(constructorArg(), INDEX);
parser.declareObjectArray(optionalConstructorArg(), lenient ? DestAlias.LENIENT_PARSER : DestAlias.STRICT_PARSER, ALIASES);
parser.declareString(optionalConstructorArg(), PIPELINE);
return parser;
}

private final String index;
private final List<DestAlias> aliases;
private final String pipeline;

public DestConfig(String index, String pipeline) {
public DestConfig(String index, List<DestAlias> aliases, String pipeline) {
this.index = ExceptionsHelper.requireNonNull(index, INDEX.getPreferredName());
this.aliases = aliases;
this.pipeline = pipeline;
}

public DestConfig(final StreamInput in) throws IOException {
index = in.readString();
if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_8_0)) {
aliases = in.readOptionalList(DestAlias::new);
} else {
aliases = null;
}
pipeline = in.readOptionalString();
}

public String getIndex() {
return index;
}

public List<DestAlias> getAliases() {
return aliases != null ? aliases : List.of();
}

public String getPipeline() {
return pipeline;
}
Expand All @@ -80,13 +96,19 @@ public void checkForDeprecations(String id, NamedXContentRegistry namedXContentR
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(index);
if (out.getTransportVersion().onOrAfter(TransportVersion.V_8_8_0)) {
out.writeOptionalCollection(aliases);
}
out.writeOptionalString(pipeline);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(INDEX.getPreferredName(), index);
if (aliases != null) {
builder.field(ALIASES.getPreferredName(), aliases);
}
if (pipeline != null) {
builder.field(PIPELINE.getPreferredName(), pipeline);
}
Expand All @@ -104,12 +126,12 @@ public boolean equals(Object other) {
}

DestConfig that = (DestConfig) other;
return Objects.equals(index, that.index) && Objects.equals(pipeline, that.pipeline);
return Objects.equals(index, that.index) && Objects.equals(aliases, that.aliases) && Objects.equals(pipeline, that.pipeline);
}

@Override
public int hashCode() {
return Objects.hash(index, pipeline);
return Objects.hash(index, aliases, pipeline);
}

public static DestConfig fromXContent(final XContentParser parser, boolean lenient) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected Request createTestInstance() {
TransformConfig config = new TransformConfig(
"transform-preview",
randomSourceConfig(),
new DestConfig("unused-transform-preview-index", null),
new DestConfig("unused-transform-preview-index", null, null),
null,
randomBoolean() ? TransformConfigTests.randomSyncConfig() : null,
null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.transform.transforms;

import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.core.transform.AbstractSerializingTransformTestCase;
import org.junit.Before;

import java.io.IOException;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

public class DestAliasTests extends AbstractSerializingTransformTestCase<DestAlias> {

private boolean lenient;

public static DestAlias randomDestAlias() {
return new DestAlias(randomAlphaOfLength(10), randomBoolean());
}

@Before
public void setRandomFeatures() {
lenient = randomBoolean();
}

@Override
protected DestAlias doParseInstance(XContentParser parser) throws IOException {
return DestAlias.fromXContent(parser, lenient);
}

@Override
protected boolean supportsUnknownFields() {
return lenient;
}

@Override
protected DestAlias createTestInstance() {
return randomDestAlias();
}

@Override
protected DestAlias mutateInstance(DestAlias instance) {
return new DestAlias(instance.getAlias() + "-x", instance.isMoveOnCreation() == false);
}

@Override
protected Reader<DestAlias> instanceReader() {
return DestAlias::new;
}

public void testFailOnEmptyAlias() throws IOException {
boolean lenient2 = randomBoolean();
String json = "{ \"alias\": \"\" }";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, json)) {
DestAlias destAlias = DestAlias.fromXContent(parser, lenient2);
assertThat(destAlias.getAlias(), is(emptyString()));
ValidationException validationException = destAlias.validate(null);
assertThat(validationException, is(notNullValue()));
assertThat(validationException.getMessage(), containsString("dest.aliases.alias must not be empty"));
}
}
}

0 comments on commit 2b70165

Please sign in to comment.