[Connector API] Support soft-deletes of connectors (#118669)

* [Connector API] Add interface for soft-deletes

* Define connector deleted system index

* Got soft-delete logic working

* Add unit tests

* Add yaml e2e test and attempt to update permissions

* Fix permissions

* Update docs

* Fix docs

* Update docs/changelog/118282.yaml

* Change logic

* Fix tests

* Remove unnecessary privilege from yaml rest test

* Update changelog

* Update docs/changelog/118669.yaml

* Adapt yaml tests

* Undo changes to muted-tests.yml

* Fix compilation issue after other PR got merged

* Exclude soft-deleted connector from checks about index_name already in use

* Update docs/reference/connector/apis/get-connector-api.asciidoc

Co-authored-by: Tim Grein <tim@4greins.de>

* Update rest-api-spec/src/main/resources/rest-api-spec/api/connector.list.json

Co-authored-by: Tim Grein <tim@4greins.de>

* Adapt comments, add connector wire serializing test

* Introduce new transport versions for passing the delete flag

* Get rid of wire serialisation, use include_deleted instead of deleted flag

* Remove unused import

* Final tweaks

* Adapt variable name in rest layer

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Tim Grein <tim@4greins.de>
This commit is contained in:
Jedr Blaszyk 2025-01-09 13:20:28 +01:00 committed by GitHub
parent 39c2dd8df1
commit 750a0ab846
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 444 additions and 401 deletions

View file

@ -0,0 +1,5 @@
pr: 118669
summary: "[Connector API] Support soft-deletes of connectors"
area: Extract&Transform
type: feature
issues: []

View file

@ -6,14 +6,14 @@
beta::[] beta::[]
.New API reference .New API reference
[sidebar] [sidebar]
-- --
For the most up-to-date API details, refer to {api-es}/group/endpoint-connector[Connector APIs]. For the most up-to-date API details, refer to {api-es}/group/endpoint-connector[Connector APIs].
-- --
Removes a connector and associated sync jobs. Soft-deletes a connector and removes associated sync jobs.
This is a destructive action that is not recoverable.
Note: this action doesn't delete any API key, ingest pipeline or data index associated with the connector. These need to be removed manually. Note: this action doesn't delete any API key, ingest pipeline or data index associated with the connector. These need to be removed manually.

View file

@ -33,6 +33,9 @@ To get started with Connector APIs, check out <<es-connectors-tutorial-api, our
`<connector_id>`:: `<connector_id>`::
(Required, string) (Required, string)
`include_deleted`::
(Optional, boolean) A flag indicating whether to also return connectors that have been soft-deleted. Defaults to `false`.
[[get-connector-api-response-codes]] [[get-connector-api-response-codes]]
==== {api-response-codes-title} ==== {api-response-codes-title}

View file

@ -47,6 +47,9 @@ To get started with Connector APIs, check out <<es-connectors-tutorial-api, our
`service_type`:: `service_type`::
(Optional, string) A comma-separated list of connector service types, used to filter search results. (Optional, string) A comma-separated list of connector service types, used to filter search results.
`include_deleted`::
(Optional, boolean) A flag indicating whether to also return connectors that have been soft-deleted. Defaults to `false`.
[[list-connector-api-example]] [[list-connector-api-example]]
==== {api-examples-title} ==== {api-examples-title}

View file

@ -26,6 +26,13 @@
} }
} }
] ]
},
"params": {
"include_deleted": {
"type": "boolean",
"default": false,
"description": "A flag indicating whether to return connectors that have been soft-deleted."
}
} }
} }
} }

View file

@ -47,6 +47,11 @@
"query": { "query": {
"type": "string", "type": "string",
"description": "A search string for querying connectors, filtering results by matching against connector names, descriptions, and index names" "description": "A search string for querying connectors, filtering results by matching against connector names, descriptions, and index names"
},
"include_deleted": {
"type": "boolean",
"default": false,
"description": "A flag indicating whether to return connectors that have been soft-deleted."
} }
} }
} }

View file

@ -302,6 +302,9 @@
}, },
"sync_now": { "sync_now": {
"type": "boolean" "type": "boolean"
},
"deleted": {
"type": "boolean"
} }
} }
} }

View file

@ -218,6 +218,30 @@ setup:
body: body:
index_name: search-test index_name: search-test
---
'Create Connector - Index name used by deleted connector':
- do:
connector.put:
connector_id: test-connector-1
body:
index_name: search-test
- match: { result: 'created' }
- do:
connector.delete:
connector_id: test-connector-1
- match: { acknowledged: true }
- do:
connector.put:
connector_id: test-connector-2
body:
index_name: search-test
- match: { result: 'created' }
--- ---
'Create Connector - Without index attached': 'Create Connector - Without index attached':
- do: - do:

View file

@ -71,7 +71,7 @@ setup:
- match: { results.1.language: "nl" } - match: { results.1.language: "nl" }
--- ---
"List Connector- with size": "List Connector - with size":
- do: - do:
connector.list: connector.list:
size: 2 size: 2
@ -106,7 +106,6 @@ setup:
- match: { count: 0 } - match: { count: 0 }
--- ---
"List Connector - filter by index names": "List Connector - filter by index names":
- do: - do:
@ -279,4 +278,145 @@ setup:
connector.list: { } connector.list: { }
---
"List Connectors - Soft deleted connectors / no deleted":
- requires:
cluster_features: ["connector_soft_deletes"]
reason: Soft deletes were introduced in 9.0 release
- do:
connector.list:
include_deleted: true
- match: { count: 3 }
---
"List Connectors - Single soft deleted connector":
- requires:
cluster_features: ["connector_soft_deletes"]
reason: Soft deletes were introduced in 9.0 release
- do:
connector.delete:
connector_id: connector-a
- do:
connector.list: {}
- match: { count: 2 }
- do:
connector.list:
include_deleted: true
- match: { count: 3 }
---
"List Connectors - Soft deleted connectors":
- requires:
cluster_features: ["connector_soft_deletes"]
reason: Soft deletes were introduced in 9.0 release
- do:
connector.delete:
connector_id: connector-a
- do:
connector.delete:
connector_id: connector-b
- do:
connector.delete:
connector_id: connector-c
- do:
connector.list:
include_deleted: true
- match: { count: 3 }
# Alphabetical order by index_name for results
- match: { results.0.id: "connector-b" }
- match: { results.0.index_name: "content-search-2-test" }
- match: { results.0.language: "en" }
- match: { results.0.deleted: true }
- match: { results.1.id: "connector-a" }
- match: { results.1.index_name: "search-1-test" }
- match: { results.1.language: "pl" }
- match: { results.1.deleted: true }
- match: { results.2.id: "connector-c" }
- match: { results.2.index_name: "search-3-test" }
- match: { results.2.language: "nl" }
- match: { results.2.deleted: true }
---
"List Connectors - Soft deleted with from":
- requires:
cluster_features: ["connector_soft_deletes"]
reason: Soft deletes were introduced in 9.0 release
- do:
connector.delete:
connector_id: connector-a
- do:
connector.delete:
connector_id: connector-b
- do:
connector.delete:
connector_id: connector-c
- do:
connector.list:
from: 1
include_deleted: true
- match: { count: 3 }
# Alphabetical order by index_name for results
- match: { results.0.id: "connector-a" }
- match: { results.0.index_name: "search-1-test" }
- match: { results.0.language: "pl" }
- match: { results.0.deleted: true }
- match: { results.1.id: "connector-c" }
- match: { results.1.index_name: "search-3-test" }
- match: { results.1.language: "nl" }
- match: { results.0.deleted: true }
---
"List Connector - Soft deleted with size":
- requires:
cluster_features: ["connector_soft_deletes"]
reason: Soft deletes were introduced in 9.0 release
- do:
connector.delete:
connector_id: connector-a
- do:
connector.delete:
connector_id: connector-b
- do:
connector.delete:
connector_id: connector-c
- do:
connector.list:
size: 2
include_deleted: true
- match: { count: 3 }
# Alphabetical order by index_name for results
- match: { results.0.id: "connector-b" }
- match: { results.0.index_name: "content-search-2-test" }
- match: { results.0.language: "en" }
- match: { results.0.deleted: true }
- match: { results.1.id: "connector-a" }
- match: { results.1.index_name: "search-1-test" }
- match: { results.1.language: "pl" }
- match: { results.1.deleted: true }

View file

@ -108,6 +108,33 @@ setup:
connector_id: test-nonexistent-connector connector_id: test-nonexistent-connector
---
"Delete Connector - Supports soft deletes":
- requires:
cluster_features: ["connector_soft_deletes"]
reason: Soft deletes were introduced in 9.0 release
- do:
connector.delete:
connector_id: test-connector-to-delete
- match: { acknowledged: true }
- do:
catch: "missing"
connector.get:
connector_id: test-connector-to-delete
- do:
connector.get:
connector_id: test-connector-to-delete
include_deleted: true
- match: { id: test-connector-to-delete }
- match: { index_name: search-1-test }
- match: { service_type: super-connector }
- match: { name: my-connector }
--- ---
"Delete connector fails for unprivileged user": "Delete connector fails for unprivileged user":
- skip: - skip:

View file

@ -9,9 +9,6 @@ package org.elasticsearch.xpack.application.connector;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Nullable;
import org.elasticsearch.xcontent.ConstructingObjectParser; import org.elasticsearch.xcontent.ConstructingObjectParser;
@ -63,9 +60,10 @@ import static org.elasticsearch.xpack.application.connector.ConnectorTemplateReg
* <li>A {@link ConnectorStatus} indicating the current status of the connector.</li> * <li>A {@link ConnectorStatus} indicating the current status of the connector.</li>
* <li>A sync cursor, used for incremental syncs.</li> * <li>A sync cursor, used for incremental syncs.</li>
* <li>A boolean flag 'syncNow', which, when set, triggers an immediate synchronization operation.</li> * <li>A boolean flag 'syncNow', which, when set, triggers an immediate synchronization operation.</li>
* <li>A boolean flag 'isDeleted', when set indicates that connector has been soft-deleted. </li>
* </ul> * </ul>
*/ */
public class Connector implements NamedWriteable, ToXContentObject { public class Connector implements ToXContentObject {
public static final String NAME = Connector.class.getName().toUpperCase(Locale.ROOT); public static final String NAME = Connector.class.getName().toUpperCase(Locale.ROOT);
@ -106,6 +104,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
@Nullable @Nullable
private final Object syncCursor; private final Object syncCursor;
private final boolean syncNow; private final boolean syncNow;
private final boolean isDeleted;
/** /**
* Constructor for Connector. * Constructor for Connector.
@ -132,6 +131,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
* @param status Current status of the connector. * @param status Current status of the connector.
* @param syncCursor Position or state indicating the current point of synchronization. * @param syncCursor Position or state indicating the current point of synchronization.
* @param syncNow Flag indicating whether an immediate synchronization is requested. * @param syncNow Flag indicating whether an immediate synchronization is requested.
* @param isDeleted Flag indicating whether connector has been soft-deleted.
*/ */
private Connector( private Connector(
String connectorId, String connectorId,
@ -155,7 +155,8 @@ public class Connector implements NamedWriteable, ToXContentObject {
String serviceType, String serviceType,
ConnectorStatus status, ConnectorStatus status,
Object syncCursor, Object syncCursor,
boolean syncNow boolean syncNow,
Boolean isDeleted
) { ) {
this.connectorId = connectorId; this.connectorId = connectorId;
this.apiKeyId = apiKeyId; this.apiKeyId = apiKeyId;
@ -179,31 +180,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
this.status = status; this.status = status;
this.syncCursor = syncCursor; this.syncCursor = syncCursor;
this.syncNow = syncNow; this.syncNow = syncNow;
} this.isDeleted = isDeleted;
public Connector(StreamInput in) throws IOException {
this.connectorId = in.readOptionalString();
this.apiKeyId = in.readOptionalString();
this.apiKeySecretId = in.readOptionalString();
this.configuration = in.readMap(ConnectorConfiguration::new);
this.customScheduling = in.readMap(ConnectorCustomSchedule::new);
this.description = in.readOptionalString();
this.error = in.readOptionalString();
this.features = in.readOptionalWriteable(ConnectorFeatures::new);
this.filtering = in.readOptionalCollectionAsList(ConnectorFiltering::new);
this.syncJobFiltering = in.readOptionalWriteable(FilteringRules::new);
this.indexName = in.readOptionalString();
this.isNative = in.readBoolean();
this.language = in.readOptionalString();
this.lastSeen = in.readOptionalInstant();
this.syncInfo = in.readOptionalWriteable(ConnectorSyncInfo::new);
this.name = in.readOptionalString();
this.pipeline = in.readOptionalWriteable(ConnectorIngestPipeline::new);
this.scheduling = in.readOptionalWriteable(ConnectorScheduling::new);
this.serviceType = in.readOptionalString();
this.status = in.readEnum(ConnectorStatus.class);
this.syncCursor = in.readGenericValue();
this.syncNow = in.readBoolean();
} }
public static final ParseField ID_FIELD = new ParseField("id"); public static final ParseField ID_FIELD = new ParseField("id");
@ -226,6 +203,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
public static final ParseField STATUS_FIELD = new ParseField("status"); public static final ParseField STATUS_FIELD = new ParseField("status");
public static final ParseField SYNC_CURSOR_FIELD = new ParseField("sync_cursor"); public static final ParseField SYNC_CURSOR_FIELD = new ParseField("sync_cursor");
static final ParseField SYNC_NOW_FIELD = new ParseField("sync_now"); static final ParseField SYNC_NOW_FIELD = new ParseField("sync_now");
public static final ParseField IS_DELETED_FIELD = new ParseField("deleted");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Connector, String> PARSER = new ConstructingObjectParser<>( private static final ConstructingObjectParser<Connector, String> PARSER = new ConstructingObjectParser<>(
@ -265,7 +243,8 @@ public class Connector implements NamedWriteable, ToXContentObject {
.setServiceType((String) args[i++]) .setServiceType((String) args[i++])
.setStatus((ConnectorStatus) args[i++]) .setStatus((ConnectorStatus) args[i++])
.setSyncCursor(args[i++]) .setSyncCursor(args[i++])
.setSyncNow((Boolean) args[i]) .setSyncNow((Boolean) args[i++])
.setIsDeleted((Boolean) args[i])
.build(); .build();
} }
); );
@ -357,6 +336,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
); );
PARSER.declareObjectOrNull(optionalConstructorArg(), (p, c) -> p.map(), null, SYNC_CURSOR_FIELD); PARSER.declareObjectOrNull(optionalConstructorArg(), (p, c) -> p.map(), null, SYNC_CURSOR_FIELD);
PARSER.declareBoolean(optionalConstructorArg(), SYNC_NOW_FIELD); PARSER.declareBoolean(optionalConstructorArg(), SYNC_NOW_FIELD);
PARSER.declareBoolean(optionalConstructorArg(), IS_DELETED_FIELD);
} }
public static Connector fromXContentBytes(BytesReference source, String docId, XContentType xContentType) { public static Connector fromXContentBytes(BytesReference source, String docId, XContentType xContentType) {
@ -433,6 +413,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
builder.field(STATUS_FIELD.getPreferredName(), status.toString()); builder.field(STATUS_FIELD.getPreferredName(), status.toString());
} }
builder.field(SYNC_NOW_FIELD.getPreferredName(), syncNow); builder.field(SYNC_NOW_FIELD.getPreferredName(), syncNow);
builder.field(IS_DELETED_FIELD.getPreferredName(), isDeleted);
} }
@Override @Override
@ -445,32 +426,6 @@ public class Connector implements NamedWriteable, ToXContentObject {
return builder; return builder;
} }
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalString(connectorId);
out.writeOptionalString(apiKeyId);
out.writeOptionalString(apiKeySecretId);
out.writeMap(configuration, StreamOutput::writeWriteable);
out.writeMap(customScheduling, StreamOutput::writeWriteable);
out.writeOptionalString(description);
out.writeOptionalString(error);
out.writeOptionalWriteable(features);
out.writeOptionalCollection(filtering);
out.writeOptionalWriteable(syncJobFiltering);
out.writeOptionalString(indexName);
out.writeBoolean(isNative);
out.writeOptionalString(language);
out.writeOptionalInstant(lastSeen);
out.writeOptionalWriteable(syncInfo);
out.writeOptionalString(name);
out.writeOptionalWriteable(pipeline);
out.writeOptionalWriteable(scheduling);
out.writeOptionalString(serviceType);
out.writeEnum(status);
out.writeGenericValue(syncCursor);
out.writeBoolean(syncNow);
}
public String getConnectorId() { public String getConnectorId() {
return connectorId; return connectorId;
} }
@ -559,6 +514,10 @@ public class Connector implements NamedWriteable, ToXContentObject {
return syncNow; return syncNow;
} }
public boolean isDeleted() {
return isDeleted;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -566,6 +525,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
Connector connector = (Connector) o; Connector connector = (Connector) o;
return isNative == connector.isNative return isNative == connector.isNative
&& syncNow == connector.syncNow && syncNow == connector.syncNow
&& isDeleted == connector.isDeleted
&& Objects.equals(connectorId, connector.connectorId) && Objects.equals(connectorId, connector.connectorId)
&& Objects.equals(apiKeyId, connector.apiKeyId) && Objects.equals(apiKeyId, connector.apiKeyId)
&& Objects.equals(apiKeySecretId, connector.apiKeySecretId) && Objects.equals(apiKeySecretId, connector.apiKeySecretId)
@ -612,15 +572,11 @@ public class Connector implements NamedWriteable, ToXContentObject {
serviceType, serviceType,
status, status,
syncCursor, syncCursor,
syncNow syncNow,
isDeleted
); );
} }
@Override
public String getWriteableName() {
return NAME;
}
public static class Builder { public static class Builder {
private String connectorId; private String connectorId;
@ -645,6 +601,7 @@ public class Connector implements NamedWriteable, ToXContentObject {
private ConnectorStatus status = ConnectorStatus.CREATED; private ConnectorStatus status = ConnectorStatus.CREATED;
private Object syncCursor; private Object syncCursor;
private boolean syncNow; private boolean syncNow;
private boolean isDeleted;
public Builder setConnectorId(String connectorId) { public Builder setConnectorId(String connectorId) {
this.connectorId = connectorId; this.connectorId = connectorId;
@ -756,6 +713,11 @@ public class Connector implements NamedWriteable, ToXContentObject {
return this; return this;
} }
public Builder setIsDeleted(Boolean isDeleted) {
this.isDeleted = Objects.requireNonNullElse(isDeleted, false);
return this;
}
public Connector build() { public Connector build() {
return new Connector( return new Connector(
connectorId, connectorId,
@ -779,7 +741,8 @@ public class Connector implements NamedWriteable, ToXContentObject {
serviceType, serviceType,
status, status,
syncCursor, syncCursor,
syncNow syncNow,
isDeleted
); );
} }
} }

View file

@ -13,9 +13,6 @@ import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DelegatingActionListener; import org.elasticsearch.action.DelegatingActionListener;
import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
@ -195,9 +192,12 @@ public class ConnectorIndexService {
* Gets the {@link Connector} from the underlying index. * Gets the {@link Connector} from the underlying index.
* *
* @param connectorId The id of the connector object. * @param connectorId The id of the connector object.
* @param includeDeleted If false, returns only the non-deleted connector with the matching ID;
* if true, returns the connector with the matching ID.
* @param listener The action listener to invoke on response/failure. * @param listener The action listener to invoke on response/failure.
*/ */
public void getConnector(String connectorId, ActionListener<ConnectorSearchResult> listener) { public void getConnector(String connectorId, boolean includeDeleted, ActionListener<ConnectorSearchResult> listener) {
try { try {
final GetRequest getRequest = new GetRequest(CONNECTOR_INDEX_NAME).id(connectorId).realtime(true); final GetRequest getRequest = new GetRequest(CONNECTOR_INDEX_NAME).id(connectorId).realtime(true);
@ -212,6 +212,12 @@ public class ConnectorIndexService {
.setResultMap(getResponse.getSourceAsMap()) .setResultMap(getResponse.getSourceAsMap())
.build(); .build();
boolean connectorIsSoftDeleted = includeDeleted == false && isConnectorDeleted(connector);
if (connectorIsSoftDeleted) {
l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
return;
}
l.onResponse(connector); l.onResponse(connector);
} catch (Exception e) { } catch (Exception e) {
listener.onFailure(e); listener.onFailure(e);
@ -223,33 +229,40 @@ public class ConnectorIndexService {
} }
/** /**
* Deletes the {@link Connector} and the related instances of {@link ConnectorSyncJob} in the underlying index. * Soft deletes the {@link Connector} and optionally removes the related instances of {@link ConnectorSyncJob} in the underlying index.
* *
* @param connectorId The id of the {@link Connector}. * @param connectorId The id of the {@link Connector}.
* @param shouldDeleteSyncJobs The flag indicating if {@link ConnectorSyncJob} should also be deleted. * @param shouldDeleteSyncJobs The flag indicating if {@link ConnectorSyncJob} should also be deleted.
* @param listener The action listener to invoke on response/failure. * @param listener The action listener to invoke on response/failure.
*/ */
public void deleteConnector(String connectorId, boolean shouldDeleteSyncJobs, ActionListener<DeleteResponse> listener) { public void deleteConnector(String connectorId, boolean shouldDeleteSyncJobs, ActionListener<UpdateResponse> listener) {
final DeleteRequest deleteRequest = new DeleteRequest(CONNECTOR_INDEX_NAME).id(connectorId)
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
try { try {
client.delete(deleteRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (l, deleteResponse) -> { // ensure that if connector is soft-deleted, deleting it again results in 404
if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId))); final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).setRefreshPolicy(
WriteRequest.RefreshPolicy.IMMEDIATE
)
.doc(
new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
.id(connectorId)
.source(Map.of(Connector.IS_DELETED_FIELD.getPreferredName(), true))
);
client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, l, (ll, updateResponse) -> {
if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
ll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
return; return;
} }
if (shouldDeleteSyncJobs) { if (shouldDeleteSyncJobs) {
new ConnectorSyncJobIndexService(client).deleteAllSyncJobsByConnectorId(connectorId, l.map(r -> deleteResponse)); new ConnectorSyncJobIndexService(client).deleteAllSyncJobsByConnectorId(connectorId, ll.map(r -> updateResponse));
} else { } else {
l.onResponse(deleteResponse); ll.onResponse(updateResponse);
} }
})); }));
}));
} catch (Exception e) { } catch (Exception e) {
listener.onFailure(e); listener.onFailure(e);
} }
} }
/** /**
@ -261,6 +274,7 @@ public class ConnectorIndexService {
* @param connectorNames Filter connectors by connector names, if provided. * @param connectorNames Filter connectors by connector names, if provided.
* @param serviceTypes Filter connectors by service types, if provided. * @param serviceTypes Filter connectors by service types, if provided.
* @param searchQuery Apply a wildcard search on index name, connector name, and description, if provided. * @param searchQuery Apply a wildcard search on index name, connector name, and description, if provided.
* @param includeDeleted If false, filters to include only non-deleted connectors; if true, no filter is applied.
* @param listener Invoked with search results or upon failure. * @param listener Invoked with search results or upon failure.
*/ */
public void listConnectors( public void listConnectors(
@ -270,12 +284,13 @@ public class ConnectorIndexService {
List<String> connectorNames, List<String> connectorNames,
List<String> serviceTypes, List<String> serviceTypes,
String searchQuery, String searchQuery,
boolean includeDeleted,
ActionListener<ConnectorIndexService.ConnectorResult> listener ActionListener<ConnectorIndexService.ConnectorResult> listener
) { ) {
try { try {
final SearchSourceBuilder source = new SearchSourceBuilder().from(from) final SearchSourceBuilder source = new SearchSourceBuilder().from(from)
.size(size) .size(size)
.query(buildListQuery(indexNames, connectorNames, serviceTypes, searchQuery)) .query(buildListQuery(indexNames, connectorNames, serviceTypes, searchQuery, includeDeleted))
.fetchSource(true) .fetchSource(true)
.sort(Connector.INDEX_NAME_FIELD.getPreferredName(), SortOrder.ASC); .sort(Connector.INDEX_NAME_FIELD.getPreferredName(), SortOrder.ASC);
final SearchRequest req = new SearchRequest(CONNECTOR_INDEX_NAME).source(source); final SearchRequest req = new SearchRequest(CONNECTOR_INDEX_NAME).source(source);
@ -311,13 +326,15 @@ public class ConnectorIndexService {
* @param connectorNames List of connector names for filtering, or null/empty to skip. * @param connectorNames List of connector names for filtering, or null/empty to skip.
* @param serviceTypes List of connector service types for filtering, or null/empty to skip. * @param serviceTypes List of connector service types for filtering, or null/empty to skip.
* @param searchQuery Search query for wildcard filtering on index name, connector name, and description, or null/empty to skip. * @param searchQuery Search query for wildcard filtering on index name, connector name, and description, or null/empty to skip.
* @param includeDeleted If false, filters to include only non-deleted connectors; if true, no filter is applied.
* @return A {@link QueryBuilder} customized based on provided filters. * @return A {@link QueryBuilder} customized based on provided filters.
*/ */
private QueryBuilder buildListQuery( private QueryBuilder buildListQuery(
List<String> indexNames, List<String> indexNames,
List<String> connectorNames, List<String> connectorNames,
List<String> serviceTypes, List<String> serviceTypes,
String searchQuery String searchQuery,
boolean includeDeleted
) { ) {
boolean filterByIndexNames = indexNames != null && indexNames.isEmpty() == false; boolean filterByIndexNames = indexNames != null && indexNames.isEmpty() == false;
boolean filterByConnectorNames = indexNames != null && connectorNames.isEmpty() == false; boolean filterByConnectorNames = indexNames != null && connectorNames.isEmpty() == false;
@ -349,7 +366,12 @@ public class ConnectorIndexService {
); );
} }
} }
return usesFilter ? boolFilterQueryBuilder : new MatchAllQueryBuilder();
if (includeDeleted == false) {
boolFilterQueryBuilder.mustNot(new TermQueryBuilder(Connector.IS_DELETED_FIELD.getPreferredName(), true));
}
return boolFilterQueryBuilder;
} }
/** /**
@ -368,7 +390,7 @@ public class ConnectorIndexService {
Map<String, Object> configurationValues = request.getConfigurationValues(); Map<String, Object> configurationValues = request.getConfigurationValues();
String connectorId = request.getConnectorId(); String connectorId = request.getConnectorId();
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).setRefreshPolicy( UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).setRefreshPolicy(
WriteRequest.RefreshPolicy.IMMEDIATE WriteRequest.RefreshPolicy.IMMEDIATE
@ -600,7 +622,7 @@ public class ConnectorIndexService {
ActionListener<UpdateResponse> listener ActionListener<UpdateResponse> listener
) { ) {
try { try {
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering( List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering(
connector.getSourceRef(), connector.getSourceRef(),
XContentType.JSON XContentType.JSON
@ -660,7 +682,7 @@ public class ConnectorIndexService {
FilteringValidationInfo validation, FilteringValidationInfo validation,
ActionListener<UpdateResponse> listener ActionListener<UpdateResponse> listener
) { ) {
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
try { try {
List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering( List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering(
connector.getSourceRef(), connector.getSourceRef(),
@ -704,7 +726,7 @@ public class ConnectorIndexService {
* @param listener Listener to respond to a successful response or an error. * @param listener Listener to respond to a successful response or an error.
*/ */
public void activateConnectorDraftFiltering(String connectorId, ActionListener<UpdateResponse> listener) { public void activateConnectorDraftFiltering(String connectorId, ActionListener<UpdateResponse> listener) {
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
try { try {
List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering( List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering(
connector.getSourceRef(), connector.getSourceRef(),
@ -819,7 +841,7 @@ public class ConnectorIndexService {
String connectorId = request.getConnectorId(); String connectorId = request.getConnectorId();
boolean isNative = request.isNative(); boolean isNative = request.isNative();
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
String indexName = getConnectorIndexNameFromSearchResult(connector); String indexName = getConnectorIndexNameFromSearchResult(connector);
@ -930,7 +952,7 @@ public class ConnectorIndexService {
return; return;
} }
getConnector(connectorId, l.delegateFailure((ll, connector) -> { getConnector(connectorId, false, l.delegateFailure((ll, connector) -> {
Boolean isNativeConnector = getConnectorIsNativeFlagFromSearchResult(connector); Boolean isNativeConnector = getConnectorIsNativeFlagFromSearchResult(connector);
Boolean doesNotHaveContentPrefix = indexName != null && isValidManagedConnectorIndexName(indexName) == false; Boolean doesNotHaveContentPrefix = indexName != null && isValidManagedConnectorIndexName(indexName) == false;
@ -1013,7 +1035,7 @@ public class ConnectorIndexService {
public void updateConnectorServiceType(UpdateConnectorServiceTypeAction.Request request, ActionListener<UpdateResponse> listener) { public void updateConnectorServiceType(UpdateConnectorServiceTypeAction.Request request, ActionListener<UpdateResponse> listener) {
try { try {
String connectorId = request.getConnectorId(); String connectorId = request.getConnectorId();
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
ConnectorStatus prevStatus = getConnectorStatusFromSearchResult(connector); ConnectorStatus prevStatus = getConnectorStatusFromSearchResult(connector);
ConnectorStatus newStatus = prevStatus == ConnectorStatus.CREATED ConnectorStatus newStatus = prevStatus == ConnectorStatus.CREATED
@ -1060,7 +1082,7 @@ public class ConnectorIndexService {
try { try {
String connectorId = request.getConnectorId(); String connectorId = request.getConnectorId();
ConnectorStatus newStatus = request.getStatus(); ConnectorStatus newStatus = request.getStatus();
getConnector(connectorId, listener.delegateFailure((l, connector) -> { getConnector(connectorId, false, listener.delegateFailure((l, connector) -> {
ConnectorStatus prevStatus = getConnectorStatusFromSearchResult(connector); ConnectorStatus prevStatus = getConnectorStatusFromSearchResult(connector);
@ -1138,6 +1160,11 @@ public class ConnectorIndexService {
return (Map<String, Object>) searchResult.getResultMap().get(Connector.CONFIGURATION_FIELD.getPreferredName()); return (Map<String, Object>) searchResult.getResultMap().get(Connector.CONFIGURATION_FIELD.getPreferredName());
} }
private boolean isConnectorDeleted(ConnectorSearchResult searchResult) {
Boolean isDeletedFlag = (Boolean) searchResult.getResultMap().get(Connector.IS_DELETED_FIELD.getPreferredName());
return Boolean.TRUE.equals(isDeletedFlag);
}
private static ConnectorIndexService.ConnectorResult mapSearchResponseToConnectorList(SearchResponse response) { private static ConnectorIndexService.ConnectorResult mapSearchResponseToConnectorList(SearchResponse response) {
final List<ConnectorSearchResult> connectorResults = Arrays.stream(response.getHits().getHits()) final List<ConnectorSearchResult> connectorResults = Arrays.stream(response.getHits().getHits())
.map(ConnectorIndexService::hitToConnector) .map(ConnectorIndexService::hitToConnector)
@ -1157,7 +1184,7 @@ public class ConnectorIndexService {
/** /**
* This method determines if any documents in the connector index have the same index name as the one specified, * This method determines if any documents in the connector index have the same index name as the one specified,
* excluding the document with the given _id if it is provided. * excluding the docs marked as deleted (soft-deleted) and document with the given _id if it is provided.
* *
* @param indexName The name of the index to check for existence in the connector index. * @param indexName The name of the index to check for existence in the connector index.
* @param connectorId The ID of the {@link Connector} to exclude from the search. Can be null if no document should be excluded. * @param connectorId The ID of the {@link Connector} to exclude from the search. Can be null if no document should be excluded.
@ -1173,6 +1200,9 @@ public class ConnectorIndexService {
boolFilterQueryBuilder.must().add(new TermQueryBuilder(Connector.INDEX_NAME_FIELD.getPreferredName(), indexName)); boolFilterQueryBuilder.must().add(new TermQueryBuilder(Connector.INDEX_NAME_FIELD.getPreferredName(), indexName));
// exclude soft-deleted connectors
boolFilterQueryBuilder.mustNot(new TermQueryBuilder(Connector.IS_DELETED_FIELD.getPreferredName(), true));
// If we know the connector _id, exclude this from search query // If we know the connector _id, exclude this from search query
if (connectorId != null) { if (connectorId != null) {
boolFilterQueryBuilder.mustNot(new IdsQueryBuilder().addIds(connectorId)); boolFilterQueryBuilder.mustNot(new IdsQueryBuilder().addIds(connectorId));

View file

@ -10,6 +10,7 @@ package org.elasticsearch.xpack.application.connector.action;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
@ -25,6 +26,7 @@ import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError; import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
public class GetConnectorAction { public class GetConnectorAction {
@ -36,22 +38,25 @@ public class GetConnectorAction {
public static class Request extends ConnectorActionRequest implements ToXContentObject { public static class Request extends ConnectorActionRequest implements ToXContentObject {
private final String connectorId; private final String connectorId;
private final Boolean includeDeleted;
private static final ParseField CONNECTOR_ID_FIELD = new ParseField("connector_id"); private static final ParseField CONNECTOR_ID_FIELD = new ParseField("connector_id");
public Request(String connectorId) { private static final ParseField INCLUDE_DELETED_FIELD = new ParseField("include_deleted");
this.connectorId = connectorId;
}
public Request(StreamInput in) throws IOException { public Request(String connectorId, Boolean includeDeleted) {
super(in); this.connectorId = connectorId;
this.connectorId = in.readString(); this.includeDeleted = includeDeleted;
} }
public String getConnectorId() { public String getConnectorId() {
return connectorId; return connectorId;
} }
public Boolean getIncludeDeleted() {
return includeDeleted;
}
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; ActionRequestValidationException validationException = null;
@ -65,8 +70,7 @@ public class GetConnectorAction {
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); TransportAction.localOnly();
out.writeString(connectorId);
} }
@Override @Override
@ -74,12 +78,12 @@ public class GetConnectorAction {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Request request = (Request) o; Request request = (Request) o;
return Objects.equals(connectorId, request.connectorId); return Objects.equals(connectorId, request.connectorId) && Objects.equals(includeDeleted, request.includeDeleted);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(connectorId); return Objects.hash(connectorId, includeDeleted);
} }
@Override @Override
@ -87,6 +91,7 @@ public class GetConnectorAction {
builder.startObject(); builder.startObject();
{ {
builder.field(CONNECTOR_ID_FIELD.getPreferredName(), connectorId); builder.field(CONNECTOR_ID_FIELD.getPreferredName(), connectorId);
builder.field(INCLUDE_DELETED_FIELD.getPreferredName(), includeDeleted);
} }
builder.endObject(); builder.endObject();
return builder; return builder;
@ -95,11 +100,12 @@ public class GetConnectorAction {
private static final ConstructingObjectParser<Request, Void> PARSER = new ConstructingObjectParser<>( private static final ConstructingObjectParser<Request, Void> PARSER = new ConstructingObjectParser<>(
"get_connector_request", "get_connector_request",
false, false,
(p) -> new Request((String) p[0]) (p) -> new Request((String) p[0], (Boolean) p[1])
); );
static { static {
PARSER.declareString(constructorArg(), CONNECTOR_ID_FIELD); PARSER.declareString(constructorArg(), CONNECTOR_ID_FIELD);
PARSER.declareBoolean(optionalConstructorArg(), INCLUDE_DELETED_FIELD);
} }
public static Request parse(XContentParser parser) { public static Request parse(XContentParser parser) {

View file

@ -10,6 +10,7 @@ package org.elasticsearch.xpack.application.connector.action;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.cluster.metadata.MetadataCreateIndexService; import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
@ -46,33 +47,28 @@ public class ListConnectorAction {
private final List<String> connectorNames; private final List<String> connectorNames;
private final List<String> connectorServiceTypes; private final List<String> connectorServiceTypes;
private final String connectorSearchQuery; private final String connectorSearchQuery;
private final Boolean includeDeleted;
private static final ParseField PAGE_PARAMS_FIELD = new ParseField("pageParams"); private static final ParseField PAGE_PARAMS_FIELD = new ParseField("pageParams");
private static final ParseField INDEX_NAMES_FIELD = new ParseField("index_names"); private static final ParseField INDEX_NAMES_FIELD = new ParseField("index_names");
private static final ParseField NAMES_FIELD = new ParseField("names"); private static final ParseField NAMES_FIELD = new ParseField("names");
private static final ParseField SEARCH_QUERY_FIELD = new ParseField("query"); private static final ParseField SEARCH_QUERY_FIELD = new ParseField("query");
private static final ParseField INCLUDE_DELETED_FIELD = new ParseField("include_deleted");
public Request(StreamInput in) throws IOException {
super(in);
this.pageParams = new PageParams(in);
this.indexNames = in.readOptionalStringCollectionAsList();
this.connectorNames = in.readOptionalStringCollectionAsList();
this.connectorServiceTypes = in.readOptionalStringCollectionAsList();
this.connectorSearchQuery = in.readOptionalString();
}
public Request( public Request(
PageParams pageParams, PageParams pageParams,
List<String> indexNames, List<String> indexNames,
List<String> connectorNames, List<String> connectorNames,
List<String> serviceTypes, List<String> serviceTypes,
String connectorSearchQuery String connectorSearchQuery,
Boolean includeDeleted
) { ) {
this.pageParams = pageParams; this.pageParams = pageParams;
this.indexNames = indexNames; this.indexNames = indexNames;
this.connectorNames = connectorNames; this.connectorNames = connectorNames;
this.connectorServiceTypes = serviceTypes; this.connectorServiceTypes = serviceTypes;
this.connectorSearchQuery = connectorSearchQuery; this.connectorSearchQuery = connectorSearchQuery;
this.includeDeleted = includeDeleted;
} }
public PageParams getPageParams() { public PageParams getPageParams() {
@ -95,6 +91,10 @@ public class ListConnectorAction {
return connectorSearchQuery; return connectorSearchQuery;
} }
public Boolean getIncludeDeleted() {
return includeDeleted;
}
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; ActionRequestValidationException validationException = null;
@ -114,12 +114,7 @@ public class ListConnectorAction {
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); TransportAction.localOnly();
pageParams.writeTo(out);
out.writeOptionalStringCollection(indexNames);
out.writeOptionalStringCollection(connectorNames);
out.writeOptionalStringCollection(connectorServiceTypes);
out.writeOptionalString(connectorSearchQuery);
} }
@Override @Override
@ -131,7 +126,8 @@ public class ListConnectorAction {
&& Objects.equals(indexNames, request.indexNames) && Objects.equals(indexNames, request.indexNames)
&& Objects.equals(connectorNames, request.connectorNames) && Objects.equals(connectorNames, request.connectorNames)
&& Objects.equals(connectorServiceTypes, request.connectorServiceTypes) && Objects.equals(connectorServiceTypes, request.connectorServiceTypes)
&& Objects.equals(connectorSearchQuery, request.connectorSearchQuery); && Objects.equals(connectorSearchQuery, request.connectorSearchQuery)
&& Objects.equals(includeDeleted, request.includeDeleted);
} }
@Override @Override
@ -147,7 +143,8 @@ public class ListConnectorAction {
(List<String>) p[1], (List<String>) p[1],
(List<String>) p[2], (List<String>) p[2],
(List<String>) p[3], (List<String>) p[3],
(String) p[4] (String) p[4],
(Boolean) p[5]
) )
); );
@ -157,6 +154,7 @@ public class ListConnectorAction {
PARSER.declareStringArray(optionalConstructorArg(), NAMES_FIELD); PARSER.declareStringArray(optionalConstructorArg(), NAMES_FIELD);
PARSER.declareStringArray(optionalConstructorArg(), Connector.SERVICE_TYPE_FIELD); PARSER.declareStringArray(optionalConstructorArg(), Connector.SERVICE_TYPE_FIELD);
PARSER.declareString(optionalConstructorArg(), SEARCH_QUERY_FIELD); PARSER.declareString(optionalConstructorArg(), SEARCH_QUERY_FIELD);
PARSER.declareBoolean(optionalConstructorArg(), INCLUDE_DELETED_FIELD);
} }
public static ListConnectorAction.Request parse(XContentParser parser) { public static ListConnectorAction.Request parse(XContentParser parser) {
@ -172,6 +170,7 @@ public class ListConnectorAction {
builder.field(NAMES_FIELD.getPreferredName(), connectorNames); builder.field(NAMES_FIELD.getPreferredName(), connectorNames);
builder.field(Connector.SERVICE_TYPE_FIELD.getPreferredName(), connectorServiceTypes); builder.field(Connector.SERVICE_TYPE_FIELD.getPreferredName(), connectorServiceTypes);
builder.field(SEARCH_QUERY_FIELD.getPreferredName(), connectorSearchQuery); builder.field(SEARCH_QUERY_FIELD.getPreferredName(), connectorSearchQuery);
builder.field(INCLUDE_DELETED_FIELD.getPreferredName(), includeDeleted);
} }
builder.endObject(); builder.endObject();
return builder; return builder;

View file

@ -36,7 +36,8 @@ public class RestGetConnectorAction extends BaseRestHandler {
@Override @Override
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) { protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) {
GetConnectorAction.Request request = new GetConnectorAction.Request(restRequest.param(CONNECTOR_ID_PARAM)); Boolean includeDeleted = restRequest.paramAsBoolean("include_deleted", false);
GetConnectorAction.Request request = new GetConnectorAction.Request(restRequest.param(CONNECTOR_ID_PARAM), includeDeleted);
return channel -> client.execute(GetConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel)); return channel -> client.execute(GetConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel));
} }
} }

View file

@ -43,13 +43,15 @@ public class RestListConnectorAction extends BaseRestHandler {
List<String> connectorNames = List.of(restRequest.paramAsStringArray("connector_name", new String[0])); List<String> connectorNames = List.of(restRequest.paramAsStringArray("connector_name", new String[0]));
List<String> serviceTypes = List.of(restRequest.paramAsStringArray("service_type", new String[0])); List<String> serviceTypes = List.of(restRequest.paramAsStringArray("service_type", new String[0]));
String searchQuery = restRequest.param("query"); String searchQuery = restRequest.param("query");
Boolean includeDeleted = restRequest.paramAsBoolean("include_deleted", false);
ListConnectorAction.Request request = new ListConnectorAction.Request( ListConnectorAction.Request request = new ListConnectorAction.Request(
new PageParams(from, size), new PageParams(from, size),
indexNames, indexNames,
connectorNames, connectorNames,
serviceTypes, serviceTypes,
searchQuery searchQuery,
includeDeleted
); );
return channel -> client.execute(ListConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel)); return channel -> client.execute(ListConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel));

View file

@ -9,7 +9,7 @@ package org.elasticsearch.xpack.application.connector.action;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction; import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.client.internal.Client; import org.elasticsearch.client.internal.Client;
import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.injection.guice.Inject;
@ -17,23 +17,21 @@ import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.application.connector.ConnectorIndexService; import org.elasticsearch.xpack.application.connector.ConnectorIndexService;
public class TransportGetConnectorAction extends HandledTransportAction<GetConnectorAction.Request, GetConnectorAction.Response> { public class TransportGetConnectorAction extends TransportAction<GetConnectorAction.Request, GetConnectorAction.Response> {
protected final ConnectorIndexService connectorIndexService; protected final ConnectorIndexService connectorIndexService;
@Inject @Inject
public TransportGetConnectorAction(TransportService transportService, ActionFilters actionFilters, Client client) { public TransportGetConnectorAction(TransportService transportService, ActionFilters actionFilters, Client client) {
super( super(GetConnectorAction.NAME, actionFilters, transportService.getTaskManager(), EsExecutors.DIRECT_EXECUTOR_SERVICE);
GetConnectorAction.NAME,
transportService,
actionFilters,
GetConnectorAction.Request::new,
EsExecutors.DIRECT_EXECUTOR_SERVICE
);
this.connectorIndexService = new ConnectorIndexService(client); this.connectorIndexService = new ConnectorIndexService(client);
} }
@Override @Override
protected void doExecute(Task task, GetConnectorAction.Request request, ActionListener<GetConnectorAction.Response> listener) { protected void doExecute(Task task, GetConnectorAction.Request request, ActionListener<GetConnectorAction.Response> listener) {
connectorIndexService.getConnector(request.getConnectorId(), listener.map(GetConnectorAction.Response::new)); connectorIndexService.getConnector(
request.getConnectorId(),
request.getIncludeDeleted(),
listener.map(GetConnectorAction.Response::new)
);
} }
} }

View file

@ -9,7 +9,7 @@ package org.elasticsearch.xpack.application.connector.action;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction; import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.client.internal.Client; import org.elasticsearch.client.internal.Client;
import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.injection.guice.Inject;
@ -18,18 +18,12 @@ import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.application.connector.ConnectorIndexService; import org.elasticsearch.xpack.application.connector.ConnectorIndexService;
import org.elasticsearch.xpack.core.action.util.PageParams; import org.elasticsearch.xpack.core.action.util.PageParams;
public class TransportListConnectorAction extends HandledTransportAction<ListConnectorAction.Request, ListConnectorAction.Response> { public class TransportListConnectorAction extends TransportAction<ListConnectorAction.Request, ListConnectorAction.Response> {
protected final ConnectorIndexService connectorIndexService; protected final ConnectorIndexService connectorIndexService;
@Inject @Inject
public TransportListConnectorAction(TransportService transportService, ActionFilters actionFilters, Client client) { public TransportListConnectorAction(TransportService transportService, ActionFilters actionFilters, Client client) {
super( super(ListConnectorAction.NAME, actionFilters, transportService.getTaskManager(), EsExecutors.DIRECT_EXECUTOR_SERVICE);
ListConnectorAction.NAME,
transportService,
actionFilters,
ListConnectorAction.Request::new,
EsExecutors.DIRECT_EXECUTOR_SERVICE
);
this.connectorIndexService = new ConnectorIndexService(client); this.connectorIndexService = new ConnectorIndexService(client);
} }
@ -44,6 +38,8 @@ public class TransportListConnectorAction extends HandledTransportAction<ListCon
request.getConnectorNames(), request.getConnectorNames(),
request.getConnectorServiceTypes(), request.getConnectorServiceTypes(),
request.getConnectorSearchQuery(), request.getConnectorSearchQuery(),
request.getIncludeDeleted(),
listener.map(r -> new ListConnectorAction.Response(r.connectors(), r.totalResults())) listener.map(r -> new ListConnectorAction.Response(r.connectors(), r.totalResults()))
); );
} }

View file

@ -10,9 +10,6 @@ package org.elasticsearch.xpack.application.connector.syncjob;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Nullable;
import org.elasticsearch.xcontent.ConstructingObjectParser; import org.elasticsearch.xcontent.ConstructingObjectParser;
@ -65,7 +62,7 @@ import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstr
* <li>The hostname of the worker to run the sync job.</li> * <li>The hostname of the worker to run the sync job.</li>
* </ul> * </ul>
*/ */
public class ConnectorSyncJob implements Writeable, ToXContentObject { public class ConnectorSyncJob implements ToXContentObject {
static final ParseField CANCELATION_REQUESTED_AT_FIELD = new ParseField("cancelation_requested_at"); static final ParseField CANCELATION_REQUESTED_AT_FIELD = new ParseField("cancelation_requested_at");
@ -213,27 +210,6 @@ public class ConnectorSyncJob implements Writeable, ToXContentObject {
this.workerHostname = workerHostname; this.workerHostname = workerHostname;
} }
public ConnectorSyncJob(StreamInput in) throws IOException {
this.cancelationRequestedAt = in.readOptionalInstant();
this.canceledAt = in.readOptionalInstant();
this.completedAt = in.readOptionalInstant();
this.connector = in.readNamedWriteable(Connector.class);
this.createdAt = in.readInstant();
this.deletedDocumentCount = in.readLong();
this.error = in.readOptionalString();
this.id = in.readString();
this.indexedDocumentCount = in.readLong();
this.indexedDocumentVolume = in.readLong();
this.jobType = in.readEnum(ConnectorSyncJobType.class);
this.lastSeen = in.readOptionalInstant();
this.metadata = in.readMap(StreamInput::readString, StreamInput::readGenericValue);
this.startedAt = in.readOptionalInstant();
this.status = in.readEnum(ConnectorSyncStatus.class);
this.totalDocumentCount = in.readOptionalLong();
this.triggerMethod = in.readEnum(ConnectorSyncJobTriggerMethod.class);
this.workerHostname = in.readOptionalString();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static final ConstructingObjectParser<ConnectorSyncJob, String> PARSER = new ConstructingObjectParser<>( private static final ConstructingObjectParser<ConnectorSyncJob, String> PARSER = new ConstructingObjectParser<>(
"connector_sync_job", "connector_sync_job",
@ -548,28 +524,6 @@ public class ConnectorSyncJob implements Writeable, ToXContentObject {
return builder; return builder;
} }
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalInstant(cancelationRequestedAt);
out.writeOptionalInstant(canceledAt);
out.writeOptionalInstant(completedAt);
out.writeNamedWriteable(connector);
out.writeInstant(createdAt);
out.writeLong(deletedDocumentCount);
out.writeOptionalString(error);
out.writeString(id);
out.writeLong(indexedDocumentCount);
out.writeLong(indexedDocumentVolume);
out.writeEnum(jobType);
out.writeOptionalInstant(lastSeen);
out.writeMap(metadata, StreamOutput::writeString, StreamOutput::writeGenericValue);
out.writeOptionalInstant(startedAt);
out.writeEnum(status);
out.writeOptionalLong(totalDocumentCount);
out.writeEnum(triggerMethod);
out.writeOptionalString(workerHostname);
}
public boolean equals(Object other) { public boolean equals(Object other) {
if (this == other) return true; if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false; if (other == null || getClass() != other.getClass()) return false;

View file

@ -11,7 +11,6 @@ import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.Tuple; import org.elasticsearch.core.Tuple;
@ -112,13 +111,66 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
} }
String connectorIdToDelete = connectorIds.get(0); String connectorIdToDelete = connectorIds.get(0);
DeleteResponse resp = awaitDeleteConnector(connectorIdToDelete, false); UpdateResponse resp = awaitDeleteConnector(connectorIdToDelete, false);
assertThat(resp.status(), equalTo(RestStatus.OK)); assertThat(resp.status(), equalTo(RestStatus.OK));
expectThrows(ResourceNotFoundException.class, () -> awaitGetConnector(connectorIdToDelete)); expectThrows(ResourceNotFoundException.class, () -> awaitGetConnector(connectorIdToDelete));
expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnector(connectorIdToDelete, false)); expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnector(connectorIdToDelete, false));
} }
public void testDeleteConnector_expectSoftDeletion() throws Exception {
int numConnectors = 5;
List<String> connectorIds = new ArrayList<>();
List<Connector> connectors = new ArrayList<>();
for (int i = 0; i < numConnectors; i++) {
Connector connector = ConnectorTestUtils.getRandomConnector();
ConnectorCreateActionResponse resp = awaitCreateConnector(null, connector);
connectorIds.add(resp.getId());
connectors.add(connector);
}
String connectorIdToDelete = connectorIds.get(0);
UpdateResponse resp = awaitDeleteConnector(connectorIdToDelete, false);
assertThat(resp.status(), equalTo(RestStatus.OK));
expectThrows(ResourceNotFoundException.class, () -> awaitGetConnector(connectorIdToDelete));
expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnector(connectorIdToDelete, false));
Connector softDeletedConnector = awaitGetSoftDeletedConnector(connectorIdToDelete);
assertThat(softDeletedConnector.getConnectorId(), equalTo(connectorIdToDelete));
assertThat(softDeletedConnector.getServiceType(), equalTo(connectors.get(0).getServiceType()));
}
public void testDeleteConnector_expectSoftDeletionMultipleConnectors() throws Exception {
int numConnectors = 5;
List<String> connectorIds = new ArrayList<>();
for (int i = 0; i < numConnectors; i++) {
Connector connector = ConnectorTestUtils.getRandomConnector();
ConnectorCreateActionResponse resp = awaitCreateConnector(null, connector);
connectorIds.add(resp.getId());
}
// Delete all of them
for (int i = 0; i < numConnectors; i++) {
String connectorIdToDelete = connectorIds.get(i);
UpdateResponse resp = awaitDeleteConnector(connectorIdToDelete, false);
assertThat(resp.status(), equalTo(RestStatus.OK));
}
// Connectors were deleted from main index
for (int i = 0; i < numConnectors; i++) {
String connectorId = connectorIds.get(i);
expectThrows(ResourceNotFoundException.class, () -> awaitGetConnector(connectorId));
}
// Soft deleted connectors available in system index
for (int i = 0; i < numConnectors; i++) {
String connectorId = connectorIds.get(i);
Connector softDeletedConnector = awaitGetSoftDeletedConnector(connectorId);
assertThat(softDeletedConnector.getConnectorId(), equalTo(connectorId));
}
}
public void testUpdateConnectorConfiguration_FullConfiguration() throws Exception { public void testUpdateConnectorConfiguration_FullConfiguration() throws Exception {
Connector connector = ConnectorTestUtils.getRandomConnector(); Connector connector = ConnectorTestUtils.getRandomConnector();
String connectorId = randomUUID(); String connectorId = randomUUID();
@ -897,13 +949,13 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
assertThat(updateApiKeyIdRequest.getApiKeySecretId(), equalTo(indexedConnector.getApiKeySecretId())); assertThat(updateApiKeyIdRequest.getApiKeySecretId(), equalTo(indexedConnector.getApiKeySecretId()));
} }
private DeleteResponse awaitDeleteConnector(String connectorId, boolean deleteConnectorSyncJobs) throws Exception { private UpdateResponse awaitDeleteConnector(String connectorId, boolean deleteConnectorSyncJobs) throws Exception {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<DeleteResponse> resp = new AtomicReference<>(null); final AtomicReference<UpdateResponse> resp = new AtomicReference<>(null);
final AtomicReference<Exception> exc = new AtomicReference<>(null); final AtomicReference<Exception> exc = new AtomicReference<>(null);
connectorIndexService.deleteConnector(connectorId, deleteConnectorSyncJobs, new ActionListener<>() { connectorIndexService.deleteConnector(connectorId, deleteConnectorSyncJobs, new ActionListener<>() {
@Override @Override
public void onResponse(DeleteResponse deleteResponse) { public void onResponse(UpdateResponse deleteResponse) {
resp.set(deleteResponse); resp.set(deleteResponse);
latch.countDown(); latch.countDown();
} }
@ -956,11 +1008,19 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
return resp.get(); return resp.get();
} }
private Connector awaitGetSoftDeletedConnector(String connectorId) throws Exception {
return awaitGetConnector(connectorId, true);
}
private Connector awaitGetConnector(String connectorId) throws Exception { private Connector awaitGetConnector(String connectorId) throws Exception {
return awaitGetConnector(connectorId, false);
}
private Connector awaitGetConnector(String connectorId, Boolean isDeleted) throws Exception {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Connector> resp = new AtomicReference<>(null); final AtomicReference<Connector> resp = new AtomicReference<>(null);
final AtomicReference<Exception> exc = new AtomicReference<>(null); final AtomicReference<Exception> exc = new AtomicReference<>(null);
connectorIndexService.getConnector(connectorId, new ActionListener<>() { connectorIndexService.getConnector(connectorId, isDeleted, new ActionListener<>() {
@Override @Override
public void onResponse(ConnectorSearchResult connectorResult) { public void onResponse(ConnectorSearchResult connectorResult) {
// Serialize the sourceRef to Connector class for unit tests // Serialize the sourceRef to Connector class for unit tests
@ -994,11 +1054,23 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
List<String> names, List<String> names,
List<String> serviceTypes, List<String> serviceTypes,
String searchQuery String searchQuery
) throws Exception {
return awaitListConnector(from, size, indexNames, names, serviceTypes, searchQuery, false);
}
private ConnectorIndexService.ConnectorResult awaitListConnector(
int from,
int size,
List<String> indexNames,
List<String> names,
List<String> serviceTypes,
String searchQuery,
Boolean isDeleted
) throws Exception { ) throws Exception {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<ConnectorIndexService.ConnectorResult> resp = new AtomicReference<>(null); final AtomicReference<ConnectorIndexService.ConnectorResult> resp = new AtomicReference<>(null);
final AtomicReference<Exception> exc = new AtomicReference<>(null); final AtomicReference<Exception> exc = new AtomicReference<>(null);
connectorIndexService.listConnectors(from, size, indexNames, names, serviceTypes, searchQuery, new ActionListener<>() { connectorIndexService.listConnectors(from, size, indexNames, names, serviceTypes, searchQuery, isDeleted, new ActionListener<>() {
@Override @Override
public void onResponse(ConnectorIndexService.ConnectorResult result) { public void onResponse(ConnectorIndexService.ConnectorResult result) {
resp.set(result); resp.set(result);

View file

@ -9,43 +9,20 @@ package org.elasticsearch.xpack.application.connector;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xcontent.ToXContent; import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.XContentType;
import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import static java.util.Collections.emptyList;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
public class ConnectorTests extends ESTestCase { public class ConnectorTests extends ESTestCase {
private NamedWriteableRegistry namedWriteableRegistry;
@Before
public void registerNamedObjects() {
SearchModule searchModule = new SearchModule(Settings.EMPTY, emptyList());
List<NamedWriteableRegistry.Entry> namedWriteables = searchModule.getNamedWriteables();
namedWriteableRegistry = new NamedWriteableRegistry(namedWriteables);
}
public final void testRandomSerialization() throws IOException {
for (int runs = 0; runs < 10; runs++) {
Connector testInstance = ConnectorTestUtils.getRandomConnector();
assertTransportSerialization(testInstance);
}
}
public void testToXContent() throws IOException { public void testToXContent() throws IOException {
String connectorId = "test-connector"; String connectorId = "test-connector";
String content = XContentHelper.stripWhitespace(""" String content = XContentHelper.stripWhitespace("""
@ -383,14 +360,4 @@ public class ConnectorTests extends ESTestCase {
assertToXContentEquivalent(originalBytes, toXContent(parsed, XContentType.JSON, humanReadable), XContentType.JSON); assertToXContentEquivalent(originalBytes, toXContent(parsed, XContentType.JSON, humanReadable), XContentType.JSON);
assertThat(parsed.getApiKeySecretId(), equalTo(null)); assertThat(parsed.getApiKeySecretId(), equalTo(null));
} }
private void assertTransportSerialization(Connector testInstance) throws IOException {
Connector deserializedInstance = copyInstance(testInstance);
assertNotSame(testInstance, deserializedInstance);
assertThat(testInstance, equalTo(deserializedInstance));
}
private Connector copyInstance(Connector instance) throws IOException {
return copyWriteable(instance, namedWriteableRegistry, Connector::new);
}
} }

View file

@ -1,43 +0,0 @@
/*
* 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.test.AbstractBWCSerializationTestCase;
import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
public class GetConnectorActionRequestBWCSerializingTests extends AbstractBWCSerializationTestCase<GetConnectorAction.Request> {
@Override
protected Writeable.Reader<GetConnectorAction.Request> instanceReader() {
return GetConnectorAction.Request::new;
}
@Override
protected GetConnectorAction.Request createTestInstance() {
return new GetConnectorAction.Request(randomAlphaOfLengthBetween(1, 10));
}
@Override
protected GetConnectorAction.Request mutateInstance(GetConnectorAction.Request instance) throws IOException {
return randomValueOtherThan(instance, this::createTestInstance);
}
@Override
protected GetConnectorAction.Request doParseInstance(XContentParser parser) throws IOException {
return GetConnectorAction.Request.parse(parser);
}
@Override
protected GetConnectorAction.Request mutateInstanceForVersion(GetConnectorAction.Request instance, TransportVersion version) {
return new GetConnectorAction.Request(instance.getConnectorId());
}
}

View file

@ -1,58 +0,0 @@
/*
* 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.test.AbstractBWCSerializationTestCase;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xpack.application.EnterpriseSearchModuleTestUtils;
import org.elasticsearch.xpack.core.action.util.PageParams;
import java.io.IOException;
import java.util.List;
public class ListConnectorActionRequestBWCSerializingTests extends AbstractBWCSerializationTestCase<ListConnectorAction.Request> {
@Override
protected Writeable.Reader<ListConnectorAction.Request> instanceReader() {
return ListConnectorAction.Request::new;
}
@Override
protected ListConnectorAction.Request createTestInstance() {
PageParams pageParams = EnterpriseSearchModuleTestUtils.randomPageParams();
return new ListConnectorAction.Request(
pageParams,
List.of(generateRandomStringArray(10, 10, false)),
List.of(generateRandomStringArray(10, 10, false)),
List.of(generateRandomStringArray(10, 10, false)),
randomAlphaOfLengthBetween(3, 10)
);
}
@Override
protected ListConnectorAction.Request mutateInstance(ListConnectorAction.Request instance) throws IOException {
return randomValueOtherThan(instance, this::createTestInstance);
}
@Override
protected ListConnectorAction.Request doParseInstance(XContentParser parser) throws IOException {
return ListConnectorAction.Request.parse(parser);
}
@Override
protected ListConnectorAction.Request mutateInstanceForVersion(ListConnectorAction.Request instance, TransportVersion version) {
return new ListConnectorAction.Request(
instance.getPageParams(),
instance.getIndexNames(),
instance.getConnectorNames(),
instance.getConnectorServiceTypes(),
instance.getConnectorSearchQuery()
);
}
}

View file

@ -8,22 +8,14 @@
package org.elasticsearch.xpack.application.connector.secrets.action; package org.elasticsearch.xpack.application.connector.secrets.action;
import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersion;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xpack.application.connector.Connector;
import org.elasticsearch.xpack.application.connector.secrets.ConnectorSecretsTestUtils; import org.elasticsearch.xpack.application.connector.secrets.ConnectorSecretsTestUtils;
import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase; import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class DeleteConnectorSecretResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase<DeleteConnectorSecretResponse> { public class DeleteConnectorSecretResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase<DeleteConnectorSecretResponse> {
@Override
public NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(List.of(new NamedWriteableRegistry.Entry(Connector.class, Connector.NAME, Connector::new)));
}
@Override @Override
protected Writeable.Reader<DeleteConnectorSecretResponse> instanceReader() { protected Writeable.Reader<DeleteConnectorSecretResponse> instanceReader() {
return DeleteConnectorSecretResponse::new; return DeleteConnectorSecretResponse::new;

View file

@ -8,22 +8,14 @@
package org.elasticsearch.xpack.application.connector.secrets.action; package org.elasticsearch.xpack.application.connector.secrets.action;
import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersion;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xpack.application.connector.Connector;
import org.elasticsearch.xpack.application.connector.secrets.ConnectorSecretsTestUtils; import org.elasticsearch.xpack.application.connector.secrets.ConnectorSecretsTestUtils;
import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase; import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class GetConnectorSecretResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase<GetConnectorSecretResponse> { public class GetConnectorSecretResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase<GetConnectorSecretResponse> {
@Override
public NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(List.of(new NamedWriteableRegistry.Entry(Connector.class, Connector.NAME, Connector::new)));
}
@Override @Override
protected Writeable.Reader<GetConnectorSecretResponse> instanceReader() { protected Writeable.Reader<GetConnectorSecretResponse> instanceReader() {
return GetConnectorSecretResponse::new; return GetConnectorSecretResponse::new;

View file

@ -8,17 +8,14 @@
package org.elasticsearch.xpack.application.connector.syncjob; package org.elasticsearch.xpack.application.connector.syncjob;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.application.connector.Connector; import org.elasticsearch.xpack.application.connector.Connector;
import org.elasticsearch.xpack.application.connector.ConnectorSyncStatus; import org.elasticsearch.xpack.application.connector.ConnectorSyncStatus;
import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
@ -26,22 +23,6 @@ import static org.hamcrest.Matchers.notNullValue;
public class ConnectorSyncJobTests extends ESTestCase { public class ConnectorSyncJobTests extends ESTestCase {
private NamedWriteableRegistry namedWriteableRegistry;
@Before
public void registerNamedObjects() {
namedWriteableRegistry = new NamedWriteableRegistry(
List.of(new NamedWriteableRegistry.Entry(Connector.class, Connector.NAME, Connector::new))
);
}
public final void testRandomSerialization() throws IOException {
for (int run = 0; run < 10; run++) {
ConnectorSyncJob syncJob = ConnectorSyncJobTestUtils.getRandomConnectorSyncJob();
assertTransportSerialization(syncJob);
}
}
public void testFromXContent_WithAllFields_AllSet() throws IOException { public void testFromXContent_WithAllFields_AllSet() throws IOException {
String content = XContentHelper.stripWhitespace(""" String content = XContentHelper.stripWhitespace("""
{ {
@ -332,14 +313,4 @@ public class ConnectorSyncJobTests extends ESTestCase {
ConnectorSyncJob.syncJobConnectorFromXContentBytes(new BytesArray(content), null, XContentType.JSON); ConnectorSyncJob.syncJobConnectorFromXContentBytes(new BytesArray(content), null, XContentType.JSON);
} }
private void assertTransportSerialization(ConnectorSyncJob testInstance) throws IOException {
ConnectorSyncJob deserializedInstance = copyInstance(testInstance);
assertNotSame(testInstance, deserializedInstance);
assertThat(testInstance, equalTo(deserializedInstance));
}
private ConnectorSyncJob copyInstance(ConnectorSyncJob instance) throws IOException {
return copyWriteable(instance, namedWriteableRegistry, ConnectorSyncJob::new);
}
} }

View file

@ -8,23 +8,15 @@
package org.elasticsearch.xpack.application.connector.syncjob.action; package org.elasticsearch.xpack.application.connector.syncjob.action;
import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersion;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xpack.application.connector.Connector;
import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobTestUtils; import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobTestUtils;
import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase; import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class GetConnectorSyncJobActionResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase< public class GetConnectorSyncJobActionResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase<
GetConnectorSyncJobAction.Response> { GetConnectorSyncJobAction.Response> {
@Override
public NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(List.of(new NamedWriteableRegistry.Entry(Connector.class, Connector.NAME, Connector::new)));
}
@Override @Override
protected Writeable.Reader<GetConnectorSyncJobAction.Response> instanceReader() { protected Writeable.Reader<GetConnectorSyncJobAction.Response> instanceReader() {
return GetConnectorSyncJobAction.Response::new; return GetConnectorSyncJobAction.Response::new;

View file

@ -8,23 +8,15 @@
package org.elasticsearch.xpack.application.connector.syncjob.action; package org.elasticsearch.xpack.application.connector.syncjob.action;
import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersion;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xpack.application.connector.Connector;
import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobTestUtils; import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobTestUtils;
import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase; import org.elasticsearch.xpack.core.ml.AbstractBWCWireSerializationTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class ListConnectorSyncJobsActionResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase< public class ListConnectorSyncJobsActionResponseBWCSerializingTests extends AbstractBWCWireSerializationTestCase<
ListConnectorSyncJobsAction.Response> { ListConnectorSyncJobsAction.Response> {
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(List.of(new NamedWriteableRegistry.Entry(Connector.class, Connector.NAME, Connector::new)));
}
@Override @Override
protected Writeable.Reader<ListConnectorSyncJobsAction.Response> instanceReader() { protected Writeable.Reader<ListConnectorSyncJobsAction.Response> instanceReader() {
return ListConnectorSyncJobsAction.Response::new; return ListConnectorSyncJobsAction.Response::new;