[Connector API] Support cleaning up sync jobs when deleting a connector (#107253)

This commit is contained in:
Jedr Blaszyk 2024-04-11 11:40:19 +02:00 committed by GitHub
parent 4707deeccc
commit 07aa9cd998
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 289 additions and 18 deletions

View file

@ -0,0 +1,5 @@
pr: 107253
summary: "[Connector API] Support cleaning up sync jobs when deleting a connector"
area: Application
type: feature
issues: []

View file

@ -26,6 +26,13 @@
} }
} }
] ]
},
"params": {
"delete_sync_jobs": {
"type": "boolean",
"default": false,
"description": "Determines whether associated sync jobs are also deleted."
}
} }
} }
} }

View file

@ -30,6 +30,7 @@ dependencies {
testImplementation(testArtifact(project(xpackModule('core')))) testImplementation(testArtifact(project(xpackModule('core'))))
testImplementation project(":test:framework") testImplementation project(":test:framework")
testImplementation(project(':modules:lang-mustache')) testImplementation(project(':modules:lang-mustache'))
testImplementation(project(':modules:reindex'))
javaRestTestImplementation(project(path: xpackModule('core'))) javaRestTestImplementation(project(path: xpackModule('core')))
javaRestTestImplementation(testArtifact(project(xpackModule('core')))) javaRestTestImplementation(testArtifact(project(xpackModule('core'))))

View file

@ -26,6 +26,80 @@ setup:
connector.get: connector.get:
connector_id: test-connector-to-delete connector_id: test-connector-to-delete
---
"Delete Connector - deletes associated sync jobs":
- do:
connector_sync_job.post:
body:
id: test-connector-to-delete
job_type: full
trigger_method: on_demand
- do:
connector_sync_job.post:
body:
id: test-connector-to-delete
job_type: full
trigger_method: on_demand
- do:
connector_sync_job.post:
body:
id: test-connector-to-delete
job_type: full
trigger_method: on_demand
- do:
connector_sync_job.list:
connector_id: test-connector-to-delete
- match: { count: 3 }
- do:
connector.delete:
connector_id: test-connector-to-delete
delete_sync_jobs: true
- match: { acknowledged: true }
- do:
connector_sync_job.list:
connector_id: test-connector-to-delete
- match: { count: 0 }
---
"Delete Connector - doesn't associated sync jobs when delete_sync_jobs is false":
- do:
connector_sync_job.post:
body:
id: test-connector-to-delete
job_type: full
trigger_method: on_demand
- do:
connector_sync_job.list:
connector_id: test-connector-to-delete
- match: { count: 1 }
- do:
connector.delete:
connector_id: test-connector-to-delete
delete_sync_jobs: false
- match: { acknowledged: true }
- do:
connector_sync_job.list:
connector_id: test-connector-to-delete
- match: { count: 1 }
--- ---
"Delete Connector - Connector does not exist": "Delete Connector - Connector does not exist":
- do: - do:

View file

@ -54,6 +54,8 @@ import org.elasticsearch.xpack.application.connector.action.UpdateConnectorPipel
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorSchedulingAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorSchedulingAction;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorServiceTypeAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorServiceTypeAction;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorStatusAction; import org.elasticsearch.xpack.application.connector.action.UpdateConnectorStatusAction;
import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJob;
import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobIndexService;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
@ -253,12 +255,13 @@ public class ConnectorIndexService {
} }
/** /**
* Deletes the {@link Connector} in the underlying index. * Deletes the {@link Connector} and the related instances of {@link ConnectorSyncJob} in the underlying index.
* *
* @param connectorId The id of the connector object. * @param connectorId The id of the {@link Connector}.
* @param listener The action listener to invoke on response/failure. * @param shouldDeleteSyncJobs The flag indicating if {@link ConnectorSyncJob} should also be deleted.
* @param listener The action listener to invoke on response/failure.
*/ */
public void deleteConnector(String connectorId, ActionListener<DeleteResponse> listener) { public void deleteConnector(String connectorId, boolean shouldDeleteSyncJobs, ActionListener<DeleteResponse> listener) {
final DeleteRequest deleteRequest = new DeleteRequest(CONNECTOR_INDEX_NAME).id(connectorId) final DeleteRequest deleteRequest = new DeleteRequest(CONNECTOR_INDEX_NAME).id(connectorId)
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
@ -269,7 +272,11 @@ public class ConnectorIndexService {
l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId))); l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
return; return;
} }
l.onResponse(deleteResponse); if (shouldDeleteSyncJobs) {
new ConnectorSyncJobIndexService(client).deleteAllSyncJobsByConnectorId(connectorId, l.map(r -> deleteResponse));
} else {
l.onResponse(deleteResponse);
}
})); }));
} catch (Exception e) { } catch (Exception e) {
listener.onFailure(e); listener.onFailure(e);

View file

@ -18,6 +18,7 @@ import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@ -35,16 +36,20 @@ public class DeleteConnectorAction {
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 deleteSyncJobs;
private static final ParseField CONNECTOR_ID_FIELD = new ParseField("connector_id"); private static final ParseField CONNECTOR_ID_FIELD = new ParseField("connector_id");
private static final ParseField DELETE_SYNC_JOB_FIELD = new ParseField("delete_sync_jobs");
public Request(StreamInput in) throws IOException { public Request(StreamInput in) throws IOException {
super(in); super(in);
this.connectorId = in.readString(); this.connectorId = in.readString();
this.deleteSyncJobs = in.readBoolean();
} }
public Request(String connectorId) { public Request(String connectorId, boolean deleteSyncJobs) {
this.connectorId = connectorId; this.connectorId = connectorId;
this.deleteSyncJobs = deleteSyncJobs;
} }
@Override @Override
@ -62,10 +67,23 @@ public class DeleteConnectorAction {
return connectorId; return connectorId;
} }
public boolean shouldDeleteSyncJobs() {
return deleteSyncJobs;
}
@Override
public String[] indices() {
// When deleting a connector, corresponding sync jobs can also be deleted
return new String[] {
ConnectorTemplateRegistry.CONNECTOR_SYNC_JOBS_INDEX_NAME_PATTERN,
ConnectorTemplateRegistry.CONNECTOR_INDEX_NAME_PATTERN };
}
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeString(connectorId); out.writeString(connectorId);
out.writeBoolean(deleteSyncJobs);
} }
@Override @Override
@ -73,18 +91,19 @@ public class DeleteConnectorAction {
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 deleteSyncJobs == request.deleteSyncJobs && Objects.equals(connectorId, request.connectorId);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(connectorId); return Objects.hash(connectorId, deleteSyncJobs);
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(CONNECTOR_ID_FIELD.getPreferredName(), connectorId); builder.field(CONNECTOR_ID_FIELD.getPreferredName(), connectorId);
builder.field(DELETE_SYNC_JOB_FIELD.getPreferredName(), deleteSyncJobs);
builder.endObject(); builder.endObject();
return builder; return builder;
} }
@ -92,10 +111,11 @@ public class DeleteConnectorAction {
private static final ConstructingObjectParser<DeleteConnectorAction.Request, Void> PARSER = new ConstructingObjectParser<>( private static final ConstructingObjectParser<DeleteConnectorAction.Request, Void> PARSER = new ConstructingObjectParser<>(
"delete_connector_request", "delete_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(constructorArg(), DELETE_SYNC_JOB_FIELD);
} }
public static DeleteConnectorAction.Request parse(XContentParser parser) { public static DeleteConnectorAction.Request parse(XContentParser parser) {

View file

@ -35,7 +35,11 @@ public class RestDeleteConnectorAction extends BaseRestHandler {
@Override @Override
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
DeleteConnectorAction.Request request = new DeleteConnectorAction.Request(restRequest.param("connector_id"));
String connectorId = restRequest.param("connector_id");
boolean shouldDeleteSyncJobs = restRequest.paramAsBoolean("delete_sync_jobs", false);
DeleteConnectorAction.Request request = new DeleteConnectorAction.Request(connectorId, shouldDeleteSyncJobs);
return channel -> client.execute(DeleteConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel)); return channel -> client.execute(DeleteConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel));
} }
} }

View file

@ -43,6 +43,7 @@ public class TransportDeleteConnectorAction extends HandledTransportAction<Delet
@Override @Override
protected void doExecute(Task task, DeleteConnectorAction.Request request, ActionListener<AcknowledgedResponse> listener) { protected void doExecute(Task task, DeleteConnectorAction.Request request, ActionListener<AcknowledgedResponse> listener) {
String connectorId = request.getConnectorId(); String connectorId = request.getConnectorId();
connectorIndexService.deleteConnector(connectorId, listener.map(v -> AcknowledgedResponse.TRUE)); boolean shouldDeleteSyncJobs = request.shouldDeleteSyncJobs();
connectorIndexService.deleteConnector(connectorId, shouldDeleteSyncJobs, listener.map(v -> AcknowledgedResponse.TRUE));
} }
} }

View file

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.application.connector.syncjob; package org.elasticsearch.xpack.application.connector.syncjob;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.ResourceNotFoundException;
@ -14,6 +15,7 @@ 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.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetRequest;
@ -21,6 +23,7 @@ import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.action.update.UpdateResponse;
@ -33,6 +36,9 @@ import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
@ -58,6 +64,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
@ -586,6 +593,37 @@ public class ConnectorSyncJobIndexService {
} }
} }
/**
* Deletes all {@link ConnectorSyncJob} documents that match a specific {@link Connector} id in the underlying index.
* Gracefully handles non-existent sync job index.
*
* @param connectorId The id of the {@link Connector} to match in the sync job documents.
* @param listener The action listener to invoke on response/failure.
*/
public void deleteAllSyncJobsByConnectorId(String connectorId, ActionListener<BulkByScrollResponse> listener) {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(CONNECTOR_SYNC_JOB_INDEX_NAME).setQuery(
new TermQueryBuilder(
ConnectorSyncJob.CONNECTOR_FIELD.getPreferredName() + "." + Connector.ID_FIELD.getPreferredName(),
connectorId
)
).setRefresh(true).setIndicesOptions(IndicesOptions.fromOptions(true, true, false, false));
client.execute(DeleteByQueryAction.INSTANCE, deleteByQueryRequest, listener.delegateFailureAndWrap((l, r) -> {
final List<BulkItemResponse.Failure> bulkDeleteFailures = r.getBulkFailures();
if (bulkDeleteFailures.isEmpty() == false) {
l.onFailure(
new ElasticsearchException(
"Error deleting sync jobs associated with connector ["
+ connectorId
+ "] "
+ bulkDeleteFailures.stream().map(BulkItemResponse.Failure::getMessage).collect(Collectors.joining("\n"))
)
);
}
l.onResponse(r);
}));
}
/** /**
* Listeners that checks failures for IndexNotFoundException and DocumentMissingException, * Listeners that checks failures for IndexNotFoundException and DocumentMissingException,
* and transforms them in ResourceNotFoundException, invoking onFailure on the delegate listener. * and transforms them in ResourceNotFoundException, invoking onFailure on the delegate listener.

View file

@ -55,6 +55,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.getRandomCronExpression; import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.getRandomCronExpression;
import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.registerSimplifiedConnectorIndexTemplates;
import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
@ -66,6 +67,7 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
@Before @Before
public void setup() { public void setup() {
registerSimplifiedConnectorIndexTemplates(indicesAdmin());
this.connectorIndexService = new ConnectorIndexService(client()); this.connectorIndexService = new ConnectorIndexService(client());
} }
@ -104,11 +106,11 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
} }
String connectorIdToDelete = connectorIds.get(0); String connectorIdToDelete = connectorIds.get(0);
DeleteResponse resp = awaitDeleteConnector(connectorIdToDelete); DeleteResponse 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)); expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnector(connectorIdToDelete, false));
} }
public void testUpdateConnectorConfiguration_FullConfiguration() throws Exception { public void testUpdateConnectorConfiguration_FullConfiguration() throws Exception {
@ -526,11 +528,11 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
assertThat(updateApiKeyIdRequest.getApiKeySecretId(), equalTo(indexedConnector.getApiKeySecretId())); assertThat(updateApiKeyIdRequest.getApiKeySecretId(), equalTo(indexedConnector.getApiKeySecretId()));
} }
private DeleteResponse awaitDeleteConnector(String connectorId) throws Exception { private DeleteResponse 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<DeleteResponse> resp = new AtomicReference<>(null);
final AtomicReference<Exception> exc = new AtomicReference<>(null); final AtomicReference<Exception> exc = new AtomicReference<>(null);
connectorIndexService.deleteConnector(connectorId, new ActionListener<>() { connectorIndexService.deleteConnector(connectorId, deleteConnectorSyncJobs, new ActionListener<>() {
@Override @Override
public void onResponse(DeleteResponse deleteResponse) { public void onResponse(DeleteResponse deleteResponse) {
resp.set(deleteResponse); resp.set(deleteResponse);

View file

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.application.connector; package org.elasticsearch.xpack.application.connector;
import org.elasticsearch.client.internal.IndicesAdminClient;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.XContentType;
@ -25,6 +26,7 @@ import org.elasticsearch.xpack.application.connector.filtering.FilteringRuleCond
import org.elasticsearch.xpack.application.connector.filtering.FilteringRules; import org.elasticsearch.xpack.application.connector.filtering.FilteringRules;
import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationInfo; import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationInfo;
import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationState; import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationState;
import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJob;
import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobType; import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobType;
import org.elasticsearch.xpack.core.scheduler.Cron; import org.elasticsearch.xpack.core.scheduler.Cron;
@ -46,11 +48,53 @@ import static org.elasticsearch.test.ESTestCase.randomInt;
import static org.elasticsearch.test.ESTestCase.randomList; import static org.elasticsearch.test.ESTestCase.randomList;
import static org.elasticsearch.test.ESTestCase.randomLong; import static org.elasticsearch.test.ESTestCase.randomLong;
import static org.elasticsearch.test.ESTestCase.randomLongBetween; import static org.elasticsearch.test.ESTestCase.randomLongBetween;
import static org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry.CONNECTOR_INDEX_NAME_PATTERN;
import static org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry.CONNECTOR_SYNC_JOBS_INDEX_NAME_PATTERN;
import static org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry.CONNECTOR_SYNC_JOBS_TEMPLATE_NAME;
import static org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry.CONNECTOR_TEMPLATE_NAME;
public final class ConnectorTestUtils { public final class ConnectorTestUtils {
public static final String NULL_STRING = null; public static final String NULL_STRING = null;
/**
* Registers index templates for instances of {@link Connector} and {@link ConnectorSyncJob} with essential field mappings. This method
* only includes mappings for fields relevant to test cases, specifying field types to ensure correct ES query logic behavior.
*
* @param indicesAdminClient The Elasticsearch indices admin client used for template registration.
*/
public static void registerSimplifiedConnectorIndexTemplates(IndicesAdminClient indicesAdminClient) {
indicesAdminClient.preparePutTemplate(CONNECTOR_TEMPLATE_NAME)
.setPatterns(List.of(CONNECTOR_INDEX_NAME_PATTERN))
.setVersion(0)
.setMapping(
"service_type",
"type=keyword,store=true",
"status",
"type=keyword,store=true",
"index_name",
"type=keyword,store=true",
"configuration",
"type=object"
)
.get();
indicesAdminClient.preparePutTemplate(CONNECTOR_SYNC_JOBS_TEMPLATE_NAME)
.setPatterns(List.of(CONNECTOR_SYNC_JOBS_INDEX_NAME_PATTERN))
.setVersion(0)
.setMapping(
"job_type",
"type=keyword,store=true",
"connector.id",
"type=keyword,store=true",
"status",
"type=keyword,store=true"
)
.get();
}
public static PutConnectorAction.Request getRandomPutConnectorActionRequest() { public static PutConnectorAction.Request getRandomPutConnectorActionRequest() {
return new PutConnectorAction.Request( return new PutConnectorAction.Request(
randomAlphaOfLengthBetween(5, 15), randomAlphaOfLengthBetween(5, 15),

View file

@ -23,7 +23,7 @@ public class DeleteConnectorActionRequestBWCSerializingTests extends AbstractBWC
@Override @Override
protected DeleteConnectorAction.Request createTestInstance() { protected DeleteConnectorAction.Request createTestInstance() {
return new DeleteConnectorAction.Request(randomAlphaOfLengthBetween(1, 10)); return new DeleteConnectorAction.Request(randomAlphaOfLengthBetween(1, 10), false);
} }
@Override @Override
@ -38,6 +38,6 @@ public class DeleteConnectorActionRequestBWCSerializingTests extends AbstractBWC
@Override @Override
protected DeleteConnectorAction.Request mutateInstanceForVersion(DeleteConnectorAction.Request instance, TransportVersion version) { protected DeleteConnectorAction.Request mutateInstanceForVersion(DeleteConnectorAction.Request instance, TransportVersion version) {
return new DeleteConnectorAction.Request(instance.getConnectorId()); return new DeleteConnectorAction.Request(instance.getConnectorId(), instance.shouldDeleteSyncJobs());
} }
} }

View file

@ -20,6 +20,9 @@ import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.reindex.ReindexPlugin;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.ParseField;
@ -40,6 +43,7 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -52,6 +56,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.application.connector.ConnectorTestUtils.registerSimplifiedConnectorIndexTemplates;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@ -73,9 +78,19 @@ public class ConnectorSyncJobIndexServiceTests extends ESSingleNodeTestCase {
private String connectorTwoId; private String connectorTwoId;
private String connectorThreeId; private String connectorThreeId;
@Override
protected Collection<Class<? extends Plugin>> getPlugins() {
List<Class<? extends Plugin>> plugins = new ArrayList<>(super.getPlugins());
// Reindex plugin is required for testDeleteAllSyncJobsByConnectorId (supports delete_by_query)
plugins.add(ReindexPlugin.class);
return plugins;
}
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
registerSimplifiedConnectorIndexTemplates(indicesAdmin());
connectorOneId = createConnector(ConnectorTestUtils.getRandomConnector()); connectorOneId = createConnector(ConnectorTestUtils.getRandomConnector());
connectorTwoId = createConnector(ConnectorTestUtils.getRandomConnector()); connectorTwoId = createConnector(ConnectorTestUtils.getRandomConnector());
connectorThreeId = createConnector(ConnectorTestUtils.getRandomConnectorWithDetachedIndex()); connectorThreeId = createConnector(ConnectorTestUtils.getRandomConnectorWithDetachedIndex());
@ -188,6 +203,35 @@ public class ConnectorSyncJobIndexServiceTests extends ESSingleNodeTestCase {
expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnectorSyncJob(NON_EXISTING_SYNC_JOB_ID)); expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnectorSyncJob(NON_EXISTING_SYNC_JOB_ID));
} }
public void testDeleteAllSyncJobsByConnectorId() throws Exception {
PostConnectorSyncJobAction.Request syncJobRequest = new PostConnectorSyncJobAction.Request(
connectorOneId,
ConnectorSyncJobType.FULL,
ConnectorSyncJobTriggerMethod.ON_DEMAND
);
int numJobs = 5;
// Create 5 jobs associated with connector
for (int i = 0; i < numJobs; i++) {
awaitPutConnectorSyncJob(syncJobRequest);
}
BulkByScrollResponse response = awaitDeleteAllSyncJobsByConnectorId(connectorOneId);
// 5 jobs should be deleted
assertEquals(numJobs, response.getDeleted());
response = awaitDeleteAllSyncJobsByConnectorId(connectorOneId);
// No jobs should be deleted
assertEquals(0, response.getDeleted());
}
public void testDeleteAllSyncJobsByConnectorId_NonExistentConnector() throws Exception {
BulkByScrollResponse response = awaitDeleteAllSyncJobsByConnectorId("non-existent-connector");
// 0 jobs should be deleted
assertEquals(0, response.getDeleted());
}
public void testGetConnectorSyncJob() throws Exception { public void testGetConnectorSyncJob() throws Exception {
PostConnectorSyncJobAction.Request syncJobRequest = ConnectorSyncJobTestUtils.getRandomPostConnectorSyncJobActionRequest( PostConnectorSyncJobAction.Request syncJobRequest = ConnectorSyncJobTestUtils.getRandomPostConnectorSyncJobActionRequest(
connectorOneId connectorOneId
@ -492,7 +536,6 @@ public class ConnectorSyncJobIndexServiceTests extends ESSingleNodeTestCase {
assertThat(idOfReturnedSyncJob, equalTo(syncJobOneId)); assertThat(idOfReturnedSyncJob, equalTo(syncJobOneId));
} }
@AwaitsFix(bugUrl = "https://github.com/elastic/enterprise-search-team/issues/6351")
public void testListConnectorSyncJobs_WithConnectorOneId_GivenTwoOverallOneFromConnectorOne_ExpectOne() throws Exception { public void testListConnectorSyncJobs_WithConnectorOneId_GivenTwoOverallOneFromConnectorOne_ExpectOne() throws Exception {
PostConnectorSyncJobAction.Request requestOne = ConnectorSyncJobTestUtils.getRandomPostConnectorSyncJobActionRequest( PostConnectorSyncJobAction.Request requestOne = ConnectorSyncJobTestUtils.getRandomPostConnectorSyncJobActionRequest(
connectorOneId connectorOneId
@ -1129,6 +1172,31 @@ public class ConnectorSyncJobIndexServiceTests extends ESSingleNodeTestCase {
return resp.get(); return resp.get();
} }
private BulkByScrollResponse awaitDeleteAllSyncJobsByConnectorId(String connectorSyncJobId) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<BulkByScrollResponse> resp = new AtomicReference<>(null);
final AtomicReference<Exception> exc = new AtomicReference<>(null);
connectorSyncJobIndexService.deleteAllSyncJobsByConnectorId(connectorSyncJobId, new ActionListener<>() {
@Override
public void onResponse(BulkByScrollResponse deleteResponse) {
resp.set(deleteResponse);
latch.countDown();
}
@Override
public void onFailure(Exception e) {
exc.set(e);
latch.countDown();
}
});
assertTrue("Timeout waiting for delete request", latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS));
if (exc.get() != null) {
throw exc.get();
}
assertNotNull("Received null response from delete request", resp.get());
return resp.get();
}
private PostConnectorSyncJobAction.Response awaitPutConnectorSyncJob(PostConnectorSyncJobAction.Request syncJobRequest) private PostConnectorSyncJobAction.Response awaitPutConnectorSyncJob(PostConnectorSyncJobAction.Request syncJobRequest)
throws Exception { throws Exception {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);