Skip to content

Commit 69d2c38

Browse files
authored
[Transform] Feature Flag for CPS (elastic#141182)
Moving Transform's CPS features behind a feature flag, enabling it for snapshot builds but leaving it disabled for release builds.
1 parent 61c4ff0 commit 69d2c38

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfig.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
import org.elasticsearch.common.io.stream.StreamInput;
1414
import org.elasticsearch.common.io.stream.StreamOutput;
1515
import org.elasticsearch.common.io.stream.Writeable;
16+
import org.elasticsearch.common.util.FeatureFlag;
1617
import org.elasticsearch.core.Nullable;
1718
import org.elasticsearch.core.TimeValue;
19+
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
20+
import org.elasticsearch.transport.RemoteClusterAware;
1821
import org.elasticsearch.xcontent.ConstructingObjectParser;
1922
import org.elasticsearch.xcontent.NamedXContentRegistry;
2023
import org.elasticsearch.xcontent.ObjectParser;
@@ -44,6 +47,7 @@
4447
import java.util.Map;
4548
import java.util.Objects;
4649

50+
import static org.elasticsearch.action.ValidateActions.addValidationError;
4751
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
4852
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
4953

@@ -61,6 +65,8 @@ public final class TransformConfig implements SimpleDiffable<TransformConfig>, W
6165
public static final String NAME = "data_frame_transform_config";
6266
public static final ParseField HEADERS = new ParseField("headers");
6367

68+
public static final FeatureFlag TRANSFORM_CROSS_PROJECT = new FeatureFlag("transform_cross_project");
69+
6470
/** Specifies all the possible transform functions. */
6571
public enum Function {
6672
PIVOT,
@@ -355,6 +361,36 @@ public ActionRequestValidationException validate(ActionRequestValidationExceptio
355361
return validationException;
356362
}
357363

364+
public ActionRequestValidationException validateNoCrossProjectWhenCrossProjectIsDisabled(
365+
CrossProjectModeDecider crossProjectModeDecider,
366+
ActionRequestValidationException validationException
367+
) {
368+
if (crossProjectModeDecider.crossProjectEnabled()) {
369+
return validateNoCrossProjectWhenCrossProjectFeatureIsDisabled(TRANSFORM_CROSS_PROJECT.isEnabled(), validationException);
370+
}
371+
return validationException;
372+
}
373+
374+
// visible for testing
375+
// remove both this and validateNoCrossProjectWhenCrossProjectIsDisabled when the feature is launched
376+
ActionRequestValidationException validateNoCrossProjectWhenCrossProjectFeatureIsDisabled(
377+
boolean featureEnabled,
378+
ActionRequestValidationException validationException
379+
) {
380+
if (featureEnabled == false) {
381+
// verify there are no remote indices
382+
var indices = getSource().getIndex();
383+
var remoteIndices = RemoteClusterAware.getRemoteIndexExpressions(indices);
384+
if (remoteIndices.isEmpty() == false) {
385+
validationException = addValidationError(
386+
"Cross-project calls are not supported, but remote indices were requested: " + remoteIndices,
387+
validationException
388+
);
389+
}
390+
}
391+
return validationException;
392+
}
393+
358394
/**
359395
* Parses the transform configuration for deprecations
360396
*

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfigTests.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
1414
import org.elasticsearch.common.io.stream.StreamInput;
1515
import org.elasticsearch.common.io.stream.Writeable.Reader;
16+
import org.elasticsearch.common.settings.Settings;
1617
import org.elasticsearch.common.xcontent.XContentHelper;
1718
import org.elasticsearch.core.TimeValue;
1819
import org.elasticsearch.core.Tuple;
20+
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
1921
import org.elasticsearch.xcontent.ToXContent;
2022
import org.elasticsearch.xcontent.XContentBuilder;
2123
import org.elasticsearch.xcontent.XContentFactory;
@@ -45,6 +47,7 @@
4547
import static org.elasticsearch.xpack.core.transform.transforms.SourceConfigTests.randomInvalidSourceConfig;
4648
import static org.elasticsearch.xpack.core.transform.transforms.SourceConfigTests.randomSourceConfig;
4749
import static org.hamcrest.Matchers.contains;
50+
import static org.hamcrest.Matchers.containsString;
4851
import static org.hamcrest.Matchers.empty;
4952
import static org.hamcrest.Matchers.equalTo;
5053
import static org.hamcrest.Matchers.is;
@@ -1048,6 +1051,98 @@ public void testSerializingMetadataPreservesOrder() throws IOException {
10481051
assertThat(new ArrayList<>(transformConfig.getMetadata().keySet()), is(equalTo(List.of("d", "a", "c", "e", "b"))));
10491052
}
10501053

1054+
public void testCrossProjectWithFeatureEnabled() throws IOException {
1055+
var transformConfig = createTransformConfigFromString("""
1056+
{
1057+
"source": {
1058+
"index": "project-1:src"
1059+
},
1060+
"dest": {
1061+
"index": "dest"
1062+
},
1063+
"pivot": {
1064+
"group_by": {
1065+
"id": {
1066+
"terms": {
1067+
"field": "id"
1068+
}
1069+
}
1070+
},
1071+
"aggs": {
1072+
"avg": {
1073+
"avg": {
1074+
"field": "points"
1075+
}
1076+
}
1077+
}
1078+
}
1079+
}""", "cross-project-feature-enabled");
1080+
assertNull(transformConfig.validateNoCrossProjectWhenCrossProjectFeatureIsDisabled(true, null));
1081+
}
1082+
1083+
public void testCrossProjectWithFeatureDisabled() throws IOException {
1084+
var transformConfig = createTransformConfigFromString("""
1085+
{
1086+
"source": {
1087+
"index": "project-1:src"
1088+
},
1089+
"dest": {
1090+
"index": "dest"
1091+
},
1092+
"pivot": {
1093+
"group_by": {
1094+
"id": {
1095+
"terms": {
1096+
"field": "id"
1097+
}
1098+
}
1099+
},
1100+
"aggs": {
1101+
"avg": {
1102+
"avg": {
1103+
"field": "points"
1104+
}
1105+
}
1106+
}
1107+
}
1108+
}""", "cross-project-feature-not-enabled");
1109+
var validationException = transformConfig.validateNoCrossProjectWhenCrossProjectFeatureIsDisabled(false, null);
1110+
assertNotNull(validationException);
1111+
assertThat(
1112+
validationException.getMessage(),
1113+
containsString("Cross-project calls are not supported, but remote indices were requested: [project-1:src]")
1114+
);
1115+
}
1116+
1117+
public void testNotCrossProjectEnvironment() throws IOException {
1118+
var transformConfig = createTransformConfigFromString("""
1119+
{
1120+
"source": {
1121+
"index": "remote-1:src"
1122+
},
1123+
"dest": {
1124+
"index": "dest"
1125+
},
1126+
"pivot": {
1127+
"group_by": {
1128+
"id": {
1129+
"terms": {
1130+
"field": "id"
1131+
}
1132+
}
1133+
},
1134+
"aggs": {
1135+
"avg": {
1136+
"avg": {
1137+
"field": "points"
1138+
}
1139+
}
1140+
}
1141+
}
1142+
}""", "cross-project");
1143+
assertNull(transformConfig.validateNoCrossProjectWhenCrossProjectIsDisabled(new CrossProjectModeDecider(Settings.EMPTY), null));
1144+
}
1145+
10511146
private TransformConfig createTransformConfigFromString(String json, String id) throws IOException {
10521147
return createTransformConfigFromString(json, id, false);
10531148
}

x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/transforms/TransformPersistentTasksExecutor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.elasticsearch.cluster.routing.IndexRoutingTable;
2727
import org.elasticsearch.cluster.service.ClusterService;
2828
import org.elasticsearch.common.Randomness;
29-
import org.elasticsearch.common.ValidationException;
3029
import org.elasticsearch.common.settings.Settings;
3130
import org.elasticsearch.core.Nullable;
3231
import org.elasticsearch.core.Strings;
@@ -351,7 +350,15 @@ protected void nodeOperation(AllocatedPersistentTask task, @Nullable TransformTa
351350
return;
352351
}
353352

354-
ValidationException validationException = config.validate(null);
353+
var validationException = config.validate(null);
354+
355+
// if we had created a transform when the feature flag was enabled, but we disabled the feature flag
356+
// then verify that this transform does not use CPS features
357+
validationException = config.validateNoCrossProjectWhenCrossProjectIsDisabled(
358+
transformServices.crossProjectModeDecider(),
359+
validationException
360+
);
361+
355362
if (validationException == null) {
356363
indexerBuilder.setTransformConfig(config);
357364
transformServices.configManager().getTransformStoredDoc(transformId, false, l);

0 commit comments

Comments
 (0)