From 23d6ae52aa846df488ea20bf39e187a940689ced Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Fri, 31 May 2024 16:25:01 +0200 Subject: [PATCH 1/3] [Connector API] Implement _features endpoint --- .../api/connector.update_features.json | 38 ++++++ .../xpack/application/EnterpriseSearch.java | 5 + .../application/connector/Connector.java | 2 +- .../connector/ConnectorIndexService.java | 27 ++++ .../RestUpdateConnectorFeaturesAction.java | 48 +++++++ ...ransportUpdateConnectorFeaturesAction.java | 50 +++++++ .../action/UpdateConnectorFeaturesAction.java | 128 ++++++++++++++++++ .../connector/ConnectorIndexServiceTests.java | 18 +++ .../xpack/security/operator/Constants.java | 1 + 9 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_features.json create mode 100644 x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestUpdateConnectorFeaturesAction.java create mode 100644 x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportUpdateConnectorFeaturesAction.java create mode 100644 x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_features.json b/rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_features.json new file mode 100644 index 0000000000000..b488e19262c2e --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_features.json @@ -0,0 +1,38 @@ +{ + "connector.update_features": { + "documentation": { + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/update-connector-features-api.html", + "description": "Updates the connector features in the connector document." + }, + "stability": "experimental", + "visibility": "public", + "headers": { + "accept": [ + "application/json" + ], + "content_type": [ + "application/json" + ] + }, + "url": { + "paths": [ + { + "path": "/_connector/{connector_id}/_features", + "methods": [ + "PUT" + ], + "parts": { + "connector_id": { + "type": "string", + "description": "The unique identifier of the connector to be updated." + } + } + } + ] + }, + "body": { + "description": "An object containing the connector's features definition.", + "required": true + } + } +} diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java index bc3da1a82fba4..871bf7fb122b9 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java @@ -58,6 +58,7 @@ import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorApiKeyIdAction; import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorConfigurationAction; import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorErrorAction; +import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorFeaturesAction; import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorFilteringAction; import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorFilteringValidationAction; import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorIndexNameAction; @@ -78,6 +79,7 @@ import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorApiKeyIdAction; import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorConfigurationAction; import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorErrorAction; +import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorFeaturesAction; import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorFilteringAction; import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorFilteringValidationAction; import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorIndexNameAction; @@ -93,6 +95,7 @@ import org.elasticsearch.xpack.application.connector.action.UpdateConnectorApiKeyIdAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorConfigurationAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorErrorAction; +import org.elasticsearch.xpack.application.connector.action.UpdateConnectorFeaturesAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorFilteringAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorFilteringValidationAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorIndexNameAction; @@ -267,6 +270,7 @@ protected XPackLicenseState getLicenseState() { new ActionHandler<>(UpdateConnectorApiKeyIdAction.INSTANCE, TransportUpdateConnectorApiKeyIdAction.class), new ActionHandler<>(UpdateConnectorConfigurationAction.INSTANCE, TransportUpdateConnectorConfigurationAction.class), new ActionHandler<>(UpdateConnectorErrorAction.INSTANCE, TransportUpdateConnectorErrorAction.class), + new ActionHandler<>(UpdateConnectorFeaturesAction.INSTANCE, TransportUpdateConnectorFeaturesAction.class), new ActionHandler<>(UpdateConnectorFilteringAction.INSTANCE, TransportUpdateConnectorFilteringAction.class), new ActionHandler<>(UpdateConnectorActiveFilteringAction.INSTANCE, TransportUpdateConnectorActiveFilteringAction.class), new ActionHandler<>( @@ -368,6 +372,7 @@ public List getRestHandlers( new RestUpdateConnectorConfigurationAction(), new RestUpdateConnectorErrorAction(), new RestUpdateConnectorActiveFilteringAction(), + new RestUpdateConnectorFeaturesAction(), new RestUpdateConnectorFilteringValidationAction(), new RestUpdateConnectorFilteringAction(), new RestUpdateConnectorIndexNameAction(), diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/Connector.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/Connector.java index e9447149c7e6c..62f42d9a16ead 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/Connector.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/Connector.java @@ -213,7 +213,7 @@ public Connector(StreamInput in) throws IOException { static final ParseField CUSTOM_SCHEDULING_FIELD = new ParseField("custom_scheduling"); public static final ParseField DESCRIPTION_FIELD = new ParseField("description"); public static final ParseField ERROR_FIELD = new ParseField("error"); - static final ParseField FEATURES_FIELD = new ParseField("features"); + public static final ParseField FEATURES_FIELD = new ParseField("features"); public static final ParseField FILTERING_FIELD = new ParseField("filtering"); public static final ParseField INDEX_NAME_FIELD = new ParseField("index_name"); public static final ParseField IS_NATIVE_FIELD = new ParseField("is_native"); diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java index 50e2633bb8c76..e5314a20bdccf 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java @@ -549,6 +549,33 @@ public void updateConnectorFiltering(String connectorId, List listener) { + try { + final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc( + new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX) + .id(connectorId) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source(Map.of(Connector.FEATURES_FIELD.getPreferredName(), features)) + ); + client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (l, updateResponse) -> { + if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) { + l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId))); + return; + } + l.onResponse(updateResponse); + })); + } catch (Exception e) { + listener.onFailure(e); + } + } + /** * Updates the draft filtering in a given {@link Connector}. * diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestUpdateConnectorFeaturesAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestUpdateConnectorFeaturesAction.java new file mode 100644 index 0000000000000..48bf87b114548 --- /dev/null +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestUpdateConnectorFeaturesAction.java @@ -0,0 +1,48 @@ +/* + * 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.application.connector.action; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.xpack.application.EnterpriseSearch; + +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.PUT; + +@ServerlessScope(Scope.PUBLIC) +public class RestUpdateConnectorFeaturesAction extends BaseRestHandler { + + @Override + public String getName() { + return "connector_update_features_action"; + } + + @Override + public List routes() { + return List.of(new Route(PUT, "/" + EnterpriseSearch.CONNECTOR_API_ENDPOINT + "/{connector_id}/_features")); + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) { + UpdateConnectorFeaturesAction.Request request = UpdateConnectorFeaturesAction.Request.fromXContentBytes( + restRequest.param("connector_id"), + restRequest.content(), + restRequest.getXContentType() + ); + return channel -> client.execute( + UpdateConnectorFeaturesAction.INSTANCE, + request, + new RestToXContentListener<>(channel, ConnectorUpdateActionResponse::status) + ); + } +} diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportUpdateConnectorFeaturesAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportUpdateConnectorFeaturesAction.java new file mode 100644 index 0000000000000..c86ddf902519f --- /dev/null +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportUpdateConnectorFeaturesAction.java @@ -0,0 +1,50 @@ +/* + * 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.application.connector.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.client.internal.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.application.connector.ConnectorIndexService; + +public class TransportUpdateConnectorFeaturesAction extends HandledTransportAction< + UpdateConnectorFeaturesAction.Request, + ConnectorUpdateActionResponse> { + + protected final ConnectorIndexService connectorIndexService; + + @Inject + public TransportUpdateConnectorFeaturesAction(TransportService transportService, ActionFilters actionFilters, Client client) { + super( + UpdateConnectorFeaturesAction.NAME, + transportService, + actionFilters, + UpdateConnectorFeaturesAction.Request::new, + EsExecutors.DIRECT_EXECUTOR_SERVICE + ); + this.connectorIndexService = new ConnectorIndexService(client); + } + + @Override + protected void doExecute( + Task task, + UpdateConnectorFeaturesAction.Request request, + ActionListener listener + ) { + connectorIndexService.updateConnectorFeatures( + request.getConnectorId(), + request.getFeatures(), + listener.map(r -> new ConnectorUpdateActionResponse(r.getResult())) + ); + } +} diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java new file mode 100644 index 0000000000000..d8283d999de5c --- /dev/null +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java @@ -0,0 +1,128 @@ +/* + * 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.application.connector.action; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.xcontent.*; +import org.elasticsearch.xpack.application.connector.Connector; +import org.elasticsearch.xpack.application.connector.ConnectorFeatures; + +import java.io.IOException; +import java.util.Objects; + +import static org.elasticsearch.action.ValidateActions.addValidationError; +import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class UpdateConnectorFeaturesAction { + + public static final String NAME = "indices:data/write/xpack/connector/update_features"; + public static final ActionType INSTANCE = new ActionType<>(NAME); + + private UpdateConnectorFeaturesAction() {/* no instances */} + + public static class Request extends ConnectorActionRequest implements ToXContentObject { + + private final String connectorId; + + private final ConnectorFeatures features; + + public Request(String connectorId, ConnectorFeatures features) { + this.connectorId = connectorId; + this.features = features; + } + + public Request(StreamInput in) throws IOException { + super(in); + this.connectorId = in.readString(); + this.features = in.readOptionalWriteable(ConnectorFeatures::new); + } + + public String getConnectorId() { + return connectorId; + } + + public ConnectorFeatures getFeatures() { + return features; + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + + if (Strings.isNullOrEmpty(connectorId)) { + validationException = addValidationError("[connector_id] cannot be [null] or [\"\"].", validationException); + } + + return validationException; + } + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>( + "connector_update_features_request", + false, + ((args, connectorId) -> new UpdateConnectorFeaturesAction.Request(connectorId, (ConnectorFeatures) args[0])) + ); + + static { + PARSER.declareObject(optionalConstructorArg(), (p, c) -> ConnectorFeatures.fromXContent(p), Connector.FEATURES_FIELD); + } + + public static UpdateConnectorFeaturesAction.Request fromXContentBytes( + String connectorId, + BytesReference source, + XContentType xContentType + ) { + try (XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, source, xContentType)) { + return UpdateConnectorFeaturesAction.Request.fromXContent(parser, connectorId); + } catch (IOException e) { + throw new ElasticsearchParseException("Failed to parse: " + source.utf8ToString(), e); + } + } + + public static UpdateConnectorFeaturesAction.Request fromXContent(XContentParser parser, String connectorId) throws IOException { + return PARSER.parse(parser, connectorId); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + { + builder.field(Connector.FEATURES_FIELD.getPreferredName(), features); + } + builder.endObject(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(connectorId); + out.writeOptionalWriteable(features); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return Objects.equals(connectorId, request.connectorId) && Objects.equals(features, request.features); + } + + @Override + public int hashCode() { + return Objects.hash(connectorId, features); + } + } +} diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java index 045cb725e477c..25cfab772ba07 100644 --- a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java @@ -240,6 +240,24 @@ public void testUpdateConnectorPipeline() throws Exception { assertThat(updatedPipeline, equalTo(indexedConnector.getPipeline())); } + public void testUpdateConnectorFeatures() throws Exception { + Connector connector = ConnectorTestUtils.getRandomConnector(); + String connectorId = randomUUID(); + + ConnectorCreateActionResponse resp = awaitCreateConnector(connectorId, connector); + assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK))); + + } + + public void testUpdateConnectorFeatures_partialUpdate() throws Exception { + Connector connector = ConnectorTestUtils.getRandomConnector(); + String connectorId = randomUUID(); + + ConnectorCreateActionResponse resp = awaitCreateConnector(connectorId, connector); + assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK))); + + } + public void testUpdateConnectorFiltering() throws Exception { Connector connector = ConnectorTestUtils.getRandomConnector(); String connectorId = randomUUID(); diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index ae5af54f078dd..5561e14da980a 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -133,6 +133,7 @@ public class Constants { "indices:data/write/xpack/connector/update_api_key_id", "indices:data/write/xpack/connector/update_configuration", "indices:data/write/xpack/connector/update_error", + "indices:data/write/xpack/connector/update_features", "indices:data/write/xpack/connector/update_filtering", "indices:data/write/xpack/connector/update_filtering/activate", "indices:data/write/xpack/connector/update_filtering/draft_validation", From 3af7151a9963ab84a825b4825f7c1f8400437995 Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Mon, 3 Jun 2024 11:54:47 +0200 Subject: [PATCH 2/3] Add unit, bwc and yaml tests --- .../src/main/groovy/elasticsearch.run.gradle | 2 +- .../170_connector_update_features.yml | 108 ++++++++++++++++++ .../connector/ConnectorFeatures.java | 16 +++ .../action/UpdateConnectorFeaturesAction.java | 19 +-- .../connector/ConnectorIndexServiceTests.java | 55 +++++++++ .../connector/ConnectorTestUtils.java | 2 +- ...turesActionRequestBWCSerializingTests.java | 51 +++++++++ 7 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/170_connector_update_features.yml create mode 100644 x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesActionRequestBWCSerializingTests.java diff --git a/build-tools-internal/src/main/groovy/elasticsearch.run.gradle b/build-tools-internal/src/main/groovy/elasticsearch.run.gradle index 3a905c001d0cf..706d994199d65 100644 --- a/build-tools-internal/src/main/groovy/elasticsearch.run.gradle +++ b/build-tools-internal/src/main/groovy/elasticsearch.run.gradle @@ -30,7 +30,7 @@ testClusters.register("runTask") { setting 'xpack.security.enabled', 'true' keystore 'bootstrap.password', 'password' user username: 'elastic-admin', password: 'elastic-password', role: '_es_test_root' - numberOfNodes = 1 + numberOfNodes = 3 } } diff --git a/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/170_connector_update_features.yml b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/170_connector_update_features.yml new file mode 100644 index 0000000000000..0964e4f50ebde --- /dev/null +++ b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/170_connector_update_features.yml @@ -0,0 +1,108 @@ +setup: + - requires: + cluster_features: ["gte_v8.15.0"] + reason: Introduced in 8.15.0 + + - do: + connector.put: + connector_id: test-connector + body: + index_name: search-1-test + name: my-connector + language: pl + is_native: false + service_type: super-connector + +--- +"Update Connector Features": + - do: + connector.update_features: + connector_id: test-connector + body: + features: + document_level_security: { enabled: true } + native_connector_api_keys: { enabled: true } + incremental_sync: { enabled: false } + sync_rules: + basic: { enabled: true } + advanced: { enabled: false } + + + - match: { result: updated } + + - do: + connector.get: + connector_id: test-connector + + - match: { features.document_level_security.enabled: true } + - match: { features.native_connector_api_keys.enabled: true } + - match: { features.incremental_sync.enabled: false } + - match: { features.sync_rules.basic.enabled: true } + - match: { features.sync_rules.advanced.enabled: false } + +--- +"Update Connector Features - Partial Update": + - do: + connector.update_features: + connector_id: test-connector + body: + features: + document_level_security: { enabled: true } + + + - match: { result: updated } + + - do: + connector.get: + connector_id: test-connector + + - match: { features.document_level_security.enabled: true } + + + - do: + connector.update_features: + connector_id: test-connector + body: + features: + native_connector_api_keys: { enabled: true } + + + - match: { result: updated } + + - do: + connector.get: + connector_id: test-connector + + # Assert that existing feature remains unchanged + - match: { features.document_level_security.enabled: true } + - match: { features.native_connector_api_keys.enabled: true } + +--- +"Update Connector Features - 404 when connector doesn't exist": + - do: + catch: "missing" + connector.update_features: + connector_id: test-non-existent-connector + body: + features: + native_connector_api_keys: { enabled: true } + +--- +"Update Connector Features - 400 status code when connector_id is empty": + - do: + catch: "bad_request" + connector.update_features: + connector_id: "" + body: + features: + native_connector_api_keys: { enabled: true } + +--- +"Update Connector Features - 400 status code when payload unknown": + - do: + catch: "bad_request" + connector.update_features: + connector_id: test-connector + body: + featuresss: + not_a_feature: 12423 diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorFeatures.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorFeatures.java index 1b2e7209e41e5..0b9a72f06ad53 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorFeatures.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorFeatures.java @@ -102,6 +102,22 @@ public static ConnectorFeatures fromXContentBytes(BytesReference source, XConten } } + public FeatureEnabled getDocumentLevelSecurityEnabled() { + return documentLevelSecurityEnabled; + } + + public FeatureEnabled getIncrementalSyncEnabled() { + return incrementalSyncEnabled; + } + + public FeatureEnabled getNativeConnectorAPIKeysEnabled() { + return nativeConnectorAPIKeysEnabled; + } + + public SyncRulesFeatures getSyncRulesFeatures() { + return syncRulesFeatures; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java index d8283d999de5c..c1f62c0efe6e8 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesAction.java @@ -15,7 +15,12 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentHelper; -import org.elasticsearch.xcontent.*; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.application.connector.Connector; import org.elasticsearch.xpack.application.connector.ConnectorFeatures; @@ -68,12 +73,11 @@ public ActionRequestValidationException validate() { return validationException; } - private static final ConstructingObjectParser PARSER = - new ConstructingObjectParser<>( - "connector_update_features_request", - false, - ((args, connectorId) -> new UpdateConnectorFeaturesAction.Request(connectorId, (ConnectorFeatures) args[0])) - ); + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "connector_update_features_request", + false, + ((args, connectorId) -> new UpdateConnectorFeaturesAction.Request(connectorId, (ConnectorFeatures) args[0])) + ); static { PARSER.declareObject(optionalConstructorArg(), (p, c) -> ConnectorFeatures.fromXContent(p), Connector.FEATURES_FIELD); @@ -124,5 +128,6 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(connectorId, features); } + } } diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java index 25cfab772ba07..21a0fede4675e 100644 --- a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java @@ -56,7 +56,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.getRandomConnectorFeatures; import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.getRandomCronExpression; +import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.randomConnectorFeatureEnabled; import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.registerSimplifiedConnectorIndexTemplates; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.equalTo; @@ -247,6 +249,13 @@ public void testUpdateConnectorFeatures() throws Exception { ConnectorCreateActionResponse resp = awaitCreateConnector(connectorId, connector); assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK))); + ConnectorFeatures newFeatures = getRandomConnectorFeatures(); + + DocWriteResponse updateResponse = awaitUpdateConnectorFeatures(connectorId, newFeatures); + assertThat(updateResponse.status(), equalTo(RestStatus.OK)); + Connector indexedConnector = awaitGetConnector(connectorId); + assertThat(newFeatures, equalTo(indexedConnector.getFeatures())); + } public void testUpdateConnectorFeatures_partialUpdate() throws Exception { @@ -256,6 +265,26 @@ public void testUpdateConnectorFeatures_partialUpdate() throws Exception { ConnectorCreateActionResponse resp = awaitCreateConnector(connectorId, connector); assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK))); + ConnectorFeatures features = getRandomConnectorFeatures(); + + awaitUpdateConnectorFeatures(connectorId, features); + + Connector indexedConnector = awaitGetConnector(connectorId); + assertThat(features, equalTo(indexedConnector.getFeatures())); + + // Partial update of DLS feature + ConnectorFeatures dlsFeature = new ConnectorFeatures.Builder().setDocumentLevelSecurityEnabled(randomConnectorFeatureEnabled()) + .build(); + awaitUpdateConnectorFeatures(connectorId, dlsFeature); + indexedConnector = awaitGetConnector(connectorId); + + // Assert that partial update was applied + assertThat(dlsFeature.getDocumentLevelSecurityEnabled(), equalTo(indexedConnector.getFeatures().getDocumentLevelSecurityEnabled())); + + // Assert other features are unchanged + assertThat(features.getSyncRulesFeatures(), equalTo(indexedConnector.getFeatures().getSyncRulesFeatures())); + assertThat(features.getNativeConnectorAPIKeysEnabled(), equalTo(indexedConnector.getFeatures().getNativeConnectorAPIKeysEnabled())); + assertThat(features.getIncrementalSyncEnabled(), equalTo(indexedConnector.getFeatures().getIncrementalSyncEnabled())); } public void testUpdateConnectorFiltering() throws Exception { @@ -908,6 +937,32 @@ public void onFailure(Exception e) { return resp.get(); } + private UpdateResponse awaitUpdateConnectorFeatures(String connectorId, ConnectorFeatures features) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final AtomicReference resp = new AtomicReference<>(null); + final AtomicReference exc = new AtomicReference<>(null); + connectorIndexService.updateConnectorFeatures(connectorId, features, new ActionListener<>() { + @Override + public void onResponse(UpdateResponse indexResponse) { + resp.set(indexResponse); + latch.countDown(); + } + + @Override + public void onFailure(Exception e) { + exc.set(e); + latch.countDown(); + } + }); + + assertTrue("Timeout waiting for update features request", latch.await(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + if (exc.get() != null) { + throw exc.get(); + } + assertNotNull("Received null response from update features request", resp.get()); + return resp.get(); + } + private UpdateResponse awaitUpdateConnectorFiltering(String connectorId, List filtering) throws Exception { CountDownLatch latch = new CountDownLatch(1); final AtomicReference resp = new AtomicReference<>(null); diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java index 230de44a8f6c5..f052ef79d82fb 100644 --- a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java @@ -371,7 +371,7 @@ public static ConnectorSearchResult getRandomConnectorSearchResult() { .build(); } - private static ConnectorFeatures.FeatureEnabled randomConnectorFeatureEnabled() { + public static ConnectorFeatures.FeatureEnabled randomConnectorFeatureEnabled() { return new ConnectorFeatures.FeatureEnabled(randomBoolean()); } diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesActionRequestBWCSerializingTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesActionRequestBWCSerializingTests.java new file mode 100644 index 0000000000000..9a191dba2e525 --- /dev/null +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFeaturesActionRequestBWCSerializingTests.java @@ -0,0 +1,51 @@ +/* + * 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.application.connector.action; + +import org.elasticsearch.TransportVersion; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xpack.application.connector.ConnectorTestUtils; +import org.elasticsearch.xpack.core.ml.AbstractBWCSerializationTestCase; + +import java.io.IOException; + +public class UpdateConnectorFeaturesActionRequestBWCSerializingTests extends AbstractBWCSerializationTestCase< + UpdateConnectorFeaturesAction.Request> { + + private String connectorId; + + @Override + protected Writeable.Reader instanceReader() { + return UpdateConnectorFeaturesAction.Request::new; + } + + @Override + protected UpdateConnectorFeaturesAction.Request createTestInstance() { + this.connectorId = randomUUID(); + return new UpdateConnectorFeaturesAction.Request(connectorId, ConnectorTestUtils.getRandomConnectorFeatures()); + } + + @Override + protected UpdateConnectorFeaturesAction.Request mutateInstance(UpdateConnectorFeaturesAction.Request instance) throws IOException { + return randomValueOtherThan(instance, this::createTestInstance); + } + + @Override + protected UpdateConnectorFeaturesAction.Request doParseInstance(XContentParser parser) throws IOException { + return UpdateConnectorFeaturesAction.Request.fromXContent(parser, this.connectorId); + } + + @Override + protected UpdateConnectorFeaturesAction.Request mutateInstanceForVersion( + UpdateConnectorFeaturesAction.Request instance, + TransportVersion version + ) { + return instance; + } +} From 773baa377e740d4f234adbb16e7b145a9d2c709c Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Mon, 3 Jun 2024 11:56:07 +0200 Subject: [PATCH 3/3] Undo change numberOfNodes for local testing --- build-tools-internal/src/main/groovy/elasticsearch.run.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-tools-internal/src/main/groovy/elasticsearch.run.gradle b/build-tools-internal/src/main/groovy/elasticsearch.run.gradle index 706d994199d65..3a905c001d0cf 100644 --- a/build-tools-internal/src/main/groovy/elasticsearch.run.gradle +++ b/build-tools-internal/src/main/groovy/elasticsearch.run.gradle @@ -30,7 +30,7 @@ testClusters.register("runTask") { setting 'xpack.security.enabled', 'true' keystore 'bootstrap.password', 'password' user username: 'elastic-admin', password: 'elastic-password', role: '_es_test_root' - numberOfNodes = 3 + numberOfNodes = 1 } }