From 705a40850b61c38a9b28b63730608f6fef455159 Mon Sep 17 00:00:00 2001 From: Mark Vieira Date: Tue, 3 Aug 2021 09:09:15 -0700 Subject: [PATCH 1/9] Bump bundled JDK to 16.0.2 (#75981) --- build-tools-internal/version.properties | 6 +++--- docs/changelog/75981.yaml | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 docs/changelog/75981.yaml diff --git a/build-tools-internal/version.properties b/build-tools-internal/version.properties index c4a6b1ae1af02..a5e18172352c7 100644 --- a/build-tools-internal/version.properties +++ b/build-tools-internal/version.properties @@ -1,8 +1,8 @@ elasticsearch = 8.0.0 lucene = 8.9.0 -bundled_jdk_vendor = adoptopenjdk -bundled_jdk = 16.0.1+9 +bundled_jdk_vendor = openjdk +bundled_jdk = 16.0.2+7@d4a915d82b4c4fbb9bde534da945d746 checkstyle = 8.42 @@ -53,4 +53,4 @@ jimfs = 1.2 jimfs_guava = 30.1-jre # test framework -networknt_json_schema_validator = 1.0.48 \ No newline at end of file +networknt_json_schema_validator = 1.0.48 diff --git a/docs/changelog/75981.yaml b/docs/changelog/75981.yaml new file mode 100644 index 0000000000000..8b7d8a03136d6 --- /dev/null +++ b/docs/changelog/75981.yaml @@ -0,0 +1,9 @@ +pr: 75981 +summary: Bump bundled JDK to 16.0.2 +area: Packaging +type: upgrade +issues: [] +versions: + - v8.0.0 + - v7.14.1 + - v7.15.0 From 10a1d27c7b5336d9cedc3fef0a05a514d28249e3 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Tue, 3 Aug 2021 17:22:06 +0100 Subject: [PATCH 2/9] [ML] Deleting a job now deletes the datafeed if necessary (#76010) Previously attempting to delete a job that had a datafeed would return an exception. However, this was unnecessarily pedantic - the user would always want to delete both job and datafeed together, and would react by deleting the datafeed and then subsequently deleting the job again. This change makes the delete job API automatically delete a datafeed associated with the job. The same level of force is used for this delete datafeed request as was used on the delete job request. This means that it's possible to force-delete an open job with a started datafeed (since force-delete datafeed will automatically stop a started datafeed). It's still not possible to delete an opened job without using force. --- .../apis/delete-job.asciidoc | 6 +- x-pack/plugin/build.gradle | 4 + .../ml/action/TransportDeleteJobAction.java | 78 ++++++++++++------- .../test/ml/delete_job_force.yml | 69 +++++++++++++++- .../rest-api-spec/test/ml/jobs_crud.yml | 2 +- 5 files changed, 128 insertions(+), 31 deletions(-) diff --git a/docs/reference/ml/anomaly-detection/apis/delete-job.asciidoc b/docs/reference/ml/anomaly-detection/apis/delete-job.asciidoc index 82b20e58c78f4..316bbd287a9d9 100644 --- a/docs/reference/ml/anomaly-detection/apis/delete-job.asciidoc +++ b/docs/reference/ml/anomaly-detection/apis/delete-job.asciidoc @@ -18,8 +18,6 @@ Deletes an existing {anomaly-job}. * Requires the `manage_ml` cluster privilege. This privilege is included in the `machine_learning_admin` built-in role. -* Before you can delete a job, you must delete the {dfeeds} that are associated -with it. See <>. * Before you can delete a job, you must close it (unless you specify the `force` parameter). See <>. @@ -36,6 +34,10 @@ are granted to anyone over the `.ml-*` indices. It is not currently possible to delete multiple jobs using wildcards or a comma separated list. +If you delete a job that has a {dfeed}, the request will first attempt to +delete the {dfeed}, as though <> was called with the same +`timeout` and `force` parameters as this delete request. + [[ml-delete-job-path-parms]] == {api-path-parms-title} diff --git a/x-pack/plugin/build.gradle b/x-pack/plugin/build.gradle index 93c6bcba6cf97..f19b7e7019a0b 100644 --- a/x-pack/plugin/build.gradle +++ b/x-pack/plugin/build.gradle @@ -145,6 +145,10 @@ def v7compatibilityNotSupportedTests = { 'rollup/start_job/Test start job twice', 'service_accounts/10_basic/Test service account tokens', // https://github.com/elastic/elasticsearch/pull/75200 + // temporarily muted awaiting backport of https://github.com/elastic/elasticsearch/pull/76010 + 'ml/delete_job_force/Test force delete job that is referred by a datafeed', + 'ml/jobs_crud/Test delete job that is referred by a datafeed', + // a type field was added to cat.ml_trained_models #73660, this is a backwards compatible change. // still this is a cat api, and we don't support them with rest api compatibility. (the test would be very hard to transform too) 'ml/trained_model_cat_apis/Test cat trained models' diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteJobAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteJobAction.java index 447b61ee3fbb6..056ec315e5dbe 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteJobAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteJobAction.java @@ -33,7 +33,9 @@ import org.elasticsearch.tasks.TaskId; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.ml.MlTasks; +import org.elasticsearch.xpack.core.ml.action.DeleteDatafeedAction; import org.elasticsearch.xpack.core.ml.action.DeleteJobAction; import org.elasticsearch.xpack.core.ml.action.KillProcessAction; import org.elasticsearch.xpack.core.ml.action.PutJobAction; @@ -162,11 +164,20 @@ protected void masterOperation(Task task, DeleteJobAction.Request request, Clust }, finalListener::onFailure); - ActionListener jobExistsListener = ActionListener.wrap( + ActionListener datafeedDeleteListener = ActionListener.wrap( response -> { auditor.info(request.getJobId(), Messages.getMessage(Messages.JOB_AUDIT_DELETING, taskId)); - markJobAsDeletingIfNotUsed(request.getJobId(), taskId, markAsDeletingListener); + cancelResetTaskIfExists(request.getJobId(), ActionListener.wrap( + r -> jobConfigProvider.updateJobBlockReason(request.getJobId(), new Blocked(Blocked.Reason.DELETE, taskId), + markAsDeletingListener), + finalListener::onFailure + )); }, + finalListener::onFailure + ); + + ActionListener jobExistsListener = ActionListener.wrap( + response -> deleteDatafeedIfNecessary(request, datafeedDeleteListener), e -> { if (request.isForce() && MlTasks.getJobTask(request.getJobId(), state.getMetadata().custom(PersistentTasksCustomMetadata.TYPE)) != null) { @@ -223,15 +234,9 @@ private void forceDeleteJob( logger.debug(() -> new ParameterizedMessage("[{}] force deleting job", jobId)); // 3. Delete the job - ActionListener removeTaskListener = new ActionListener() { - @Override - public void onResponse(Boolean response) { - // use clusterService.state() here so that the updated state without the task is available - normalDeleteJob(parentTaskClient, request, clusterService.state(), listener); - } - - @Override - public void onFailure(Exception e) { + ActionListener removeTaskListener = ActionListener.wrap( + response -> normalDeleteJob(parentTaskClient, request, clusterService.state(), listener), + e -> { if (ExceptionsHelper.unwrapCause(e) instanceof ResourceNotFoundException) { // use clusterService.state() here so that the updated state without the task is available normalDeleteJob(parentTaskClient, request, clusterService.state(), listener); @@ -239,7 +244,7 @@ public void onFailure(Exception e) { listener.onFailure(e); } } - }; + ); // 2. Cancel the persistent task. This closes the process gracefully so // the process should be killed first. @@ -288,21 +293,42 @@ private void checkJobIsNotOpen(String jobId, ClusterState state) { } } - private void markJobAsDeletingIfNotUsed(String jobId, TaskId taskId, ActionListener listener) { + private void deleteDatafeedIfNecessary(DeleteJobAction.Request deleteJobRequest, ActionListener listener) { - datafeedConfigProvider.findDatafeedIdsForJobIds(Collections.singletonList(jobId), ActionListener.wrap( - datafeedIds -> { - if (datafeedIds.isEmpty() == false) { - listener.onFailure(ExceptionsHelper.conflictStatusException("Cannot delete job [" + jobId + "] because datafeed [" - + datafeedIds.iterator().next() + "] refers to it")); - return; - } - cancelResetTaskIfExists(jobId, ActionListener.wrap( - response -> jobConfigProvider.updateJobBlockReason(jobId, new Blocked(Blocked.Reason.DELETE, taskId), listener), - listener::onFailure - )); - }, - listener::onFailure + datafeedConfigProvider.findDatafeedIdsForJobIds(Collections.singletonList(deleteJobRequest.getJobId()), ActionListener.wrap( + datafeedIds -> { + // Since it's only possible to delete a single job at a time there should not be more than one datafeed + assert datafeedIds.size() <= 1 : "Expected at most 1 datafeed for a single job, got " + datafeedIds; + if (datafeedIds.isEmpty()) { + listener.onResponse(AcknowledgedResponse.TRUE); + return; + } + DeleteDatafeedAction.Request deleteDatafeedRequest = new DeleteDatafeedAction.Request(datafeedIds.iterator().next()); + deleteDatafeedRequest.setForce(deleteJobRequest.isForce()); + deleteDatafeedRequest.timeout(deleteJobRequest.timeout()); + ClientHelper.executeAsyncWithOrigin( + client, + ClientHelper.ML_ORIGIN, + DeleteDatafeedAction.INSTANCE, + deleteDatafeedRequest, + ActionListener.wrap( + listener::onResponse, + e -> { + // It's possible that a simultaneous call to delete the datafeed has deleted it in between + // us finding the datafeed ID and trying to delete it in this method - this is OK + if (ExceptionsHelper.unwrapCause(e) instanceof ResourceNotFoundException) { + listener.onResponse(AcknowledgedResponse.TRUE); + } else { + listener.onFailure(ExceptionsHelper.conflictStatusException( + "failed to delete job [{}] as its datafeed [{}] could not be deleted", e, + deleteJobRequest.getJobId(), deleteDatafeedRequest.getDatafeedId()) + ); + } + } + ) + ); + }, + listener::onFailure )); } diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/delete_job_force.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/delete_job_force.yml index 8e12056c27fed..6a79631a72408 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/delete_job_force.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/delete_job_force.yml @@ -16,6 +16,23 @@ setup: } } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + indices.create: + index: airline-data + body: + mappings: + properties: + time: + type: date + airline: + type: keyword + airport: + type: text + responsetime: + type: float + --- "Test force delete a closed job": - do: @@ -65,11 +82,59 @@ setup: body: > { "job_id":"force-delete-job", - "indexes":["index-foo"] + "indices":["index-foo"] } - match: { datafeed_id: force-delete-job-datafeed } - do: - catch: /Cannot delete job \[force-delete-job\] because datafeed \[force-delete-job-datafeed\] refers to it/ ml.delete_job: job_id: force-delete-job + - match: { acknowledged: true } + + - do: + ml.get_jobs: + job_id: "_all" + - match: { count: 0 } + + - do: + ml.get_datafeeds: + datafeed_id: "_all" + - match: { count: 0 } + +--- +"Test force delete an open job that is referred by a started datafeed": + + - do: + ml.open_job: + job_id: force-delete-job + + - do: + ml.put_datafeed: + datafeed_id: force-delete-job-started-datafeed + body: > + { + "job_id":"force-delete-job", + "indices":["airline-data"] + } + - match: { datafeed_id: force-delete-job-started-datafeed } + + - do: + ml.start_datafeed: + datafeed_id: force-delete-job-started-datafeed + start: 0 + + - do: + ml.delete_job: + force: true + job_id: force-delete-job + - match: { acknowledged: true } + + - do: + ml.get_jobs: + job_id: "_all" + - match: { count: 0 } + + - do: + ml.get_datafeeds: + datafeed_id: "_all" + - match: { count: 0 } diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/jobs_crud.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/jobs_crud.yml index 6b96b391bd68d..5370e41ea60c9 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/jobs_crud.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/jobs_crud.yml @@ -623,9 +623,9 @@ - match: { datafeed_id: "jobs-crud-test-datafeed-1" } - do: - catch: /Cannot delete job \[jobs-crud-datafeed-job\] because datafeed \[jobs-crud-test-datafeed-1\] refers to it/ ml.delete_job: job_id: jobs-crud-datafeed-job + - match: { acknowledged: true } --- "Test delete job that is opened": From b11c15b7e0af64f90c3eb9c52c2534b4f143a070 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Tue, 3 Aug 2021 13:06:14 -0400 Subject: [PATCH 3/9] [ML] Adding new trained model allocation service (#75778) Adds a new service for trained model allocation to nodes. Initially, this only supports PyTorch models and simply allocates to nodes with the ML roles. Design is fairly simple: - A master node service runs allowing for new allocations to be created/updated/deleted from cluster state - A node service runs listening to updates referencing the local node + any models it may have allocated and updates accordingly. This type of service sort of splits the difference between the logic of shard allocation and persistent tasks. Neither really fully addressed the need here. --- .idea/inspectionProfiles/Project_Default.xml | 1 + .../CreateTrainedModelAllocationAction.java | 133 ++++ .../DeleteTrainedModelAllocationAction.java | 70 ++ .../StartTrainedModelDeploymentAction.java | 30 +- ...dateTrainedModelAllocationStateAction.java | 94 +++ .../inference/allocation/AllocationState.java | 24 + .../ml/inference/allocation/RoutingState.java | 27 + .../allocation/RoutingStateAndReason.java | 96 +++ .../allocation/TrainedModelAllocation.java | 240 +++++++ .../inference/trainedmodel/IndexLocation.java | 2 +- ...inedModelAllocationActionRequestTests.java | 31 + ...nedModelAllocationActionResponseTests.java | 33 + ...inedModelAllocationActionRequestTests.java | 25 + ...odelAllocationStateActionRequestTests.java | 26 + .../allocation/AllocationStateTests.java | 23 + .../RoutingStateAndReasonTests.java | 36 + .../allocation/RoutingStateTests.java | 23 + .../TrainedModelAllocationTests.java | 143 ++++ .../xpack/ml/integration/PyTorchModelIT.java | 35 +- .../xpack/ml/MachineLearning.java | 61 +- ...ortCreateTrainedModelAllocationAction.java | 82 +++ ...ortDeleteTrainedModelAllocationAction.java | 64 ++ ...portInferTrainedModelDeploymentAction.java | 26 +- ...portStartTrainedModelDeploymentAction.java | 249 ++----- ...sportStopTrainedModelDeploymentAction.java | 151 ++-- ...dateTrainedModelAllocationStateAction.java | 64 ++ .../TrainedModelAllocationClusterService.java | 407 +++++++++++ .../TrainedModelAllocationMetadata.java | 278 ++++++++ .../TrainedModelAllocationNodeService.java | 380 ++++++++++ .../TrainedModelAllocationService.java | 173 +++++ .../deployment/DeploymentManager.java | 70 +- .../TrainedModelDeploymentTask.java | 39 +- .../xpack/ml/job/JobNodeSelector.java | 2 +- ...nedModelAllocationClusterServiceTests.java | 670 ++++++++++++++++++ .../TrainedModelAllocationMetadataTests.java | 108 +++ ...rainedModelAllocationNodeServiceTests.java | 359 ++++++++++ .../xpack/security/operator/Constants.java | 3 + 37 files changed, 3964 insertions(+), 314 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationAction.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationAction.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateAction.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationState.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingState.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReason.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocation.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionRequestTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionResponseTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationActionRequestTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateActionRequestTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationStateTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReasonTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocationTests.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportCreateTrainedModelAllocationAction.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAllocationAction.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateTrainedModelAllocationStateAction.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterService.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadata.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeService.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationService.java create mode 100644 x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterServiceTests.java create mode 100644 x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadataTests.java create mode 100644 x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeServiceTests.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 5cf789707c58c..3f3eb5218afed 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -5,5 +5,6 @@ + \ No newline at end of file diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationAction.java new file mode 100644 index 0000000000000..ccf567e76a559 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationAction.java @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ParseField; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; + +import java.io.IOException; +import java.util.Objects; + +public class CreateTrainedModelAllocationAction extends ActionType { + public static final CreateTrainedModelAllocationAction INSTANCE = new CreateTrainedModelAllocationAction(); + public static final String NAME = "cluster:internal/xpack/ml/model_allocation/create"; + + private CreateTrainedModelAllocationAction() { + super(NAME, CreateTrainedModelAllocationAction.Response::new); + } + + public static class Request extends MasterNodeRequest { + private final StartTrainedModelDeploymentAction.TaskParams taskParams; + + public Request(StartTrainedModelDeploymentAction.TaskParams taskParams) { + this.taskParams = ExceptionsHelper.requireNonNull(taskParams, "taskParams"); + } + + public Request(StreamInput in) throws IOException { + super(in); + this.taskParams = new StartTrainedModelDeploymentAction.TaskParams(in); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + taskParams.writeTo(out); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return Objects.equals(taskParams, request.taskParams); + } + + @Override + public int hashCode() { + return Objects.hash(taskParams); + } + + public StartTrainedModelDeploymentAction.TaskParams getTaskParams() { + return taskParams; + } + } + + public static class Response extends ActionResponse implements ToXContentObject { + + private static final ParseField ALLOCATION = new ParseField("allocation"); + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "create_trained_model_allocation_response", + a -> new Response((TrainedModelAllocation) a[0]) + ); + static { + PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> TrainedModelAllocation.fromXContent(p), ALLOCATION); + } + static Response fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + private final TrainedModelAllocation trainedModelAllocation; + + public Response(TrainedModelAllocation trainedModelAllocation) { + this.trainedModelAllocation = trainedModelAllocation; + } + + public Response(StreamInput in) throws IOException { + super(in); + this.trainedModelAllocation = new TrainedModelAllocation(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + trainedModelAllocation.writeTo(out); + } + + public TrainedModelAllocation getTrainedModelAllocation() { + return trainedModelAllocation; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("allocation", trainedModelAllocation); + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Response response = (Response) o; + return Objects.equals(trainedModelAllocation, response.trainedModelAllocation); + } + + @Override + public int hashCode() { + return Objects.hash(trainedModelAllocation); + } + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationAction.java new file mode 100644 index 0000000000000..589ae631dece8 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationAction.java @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; + +import java.io.IOException; +import java.util.Objects; + +public class DeleteTrainedModelAllocationAction extends ActionType { + public static final DeleteTrainedModelAllocationAction INSTANCE = new DeleteTrainedModelAllocationAction(); + public static final String NAME = "cluster:internal/xpack/ml/model_allocation/delete"; + + private DeleteTrainedModelAllocationAction() { + super(NAME, AcknowledgedResponse::readFrom); + } + + public static class Request extends MasterNodeRequest { + private final String modelId; + + public Request(String modelId) { + this.modelId = ExceptionsHelper.requireNonNull(modelId, "model_id"); + } + + public Request(StreamInput in) throws IOException { + super(in); + this.modelId = in.readString(); + } + + public String getModelId() { + return modelId; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(modelId); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return Objects.equals(modelId, request.modelId); + } + + @Override + public int hashCode() { + return Objects.hash(modelId); + } + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/StartTrainedModelDeploymentAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/StartTrainedModelDeploymentAction.java index 6e0c4d517d1b7..fb41c1d92a5ec 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/StartTrainedModelDeploymentAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/StartTrainedModelDeploymentAction.java @@ -11,11 +11,15 @@ import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionType; import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.core.TimeValue; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -31,7 +35,7 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; -public class StartTrainedModelDeploymentAction extends ActionType { +public class StartTrainedModelDeploymentAction extends ActionType { public static final StartTrainedModelDeploymentAction INSTANCE = new StartTrainedModelDeploymentAction(); public static final String NAME = "cluster:admin/xpack/ml/trained_models/deployment/start"; @@ -39,7 +43,7 @@ public class StartTrainedModelDeploymentAction extends ActionType implements ToXContentObject { @@ -120,9 +124,29 @@ public String toString() { public static class TaskParams implements PersistentTaskParams, MlTaskParams { - public static final Version VERSION_INTRODUCED = Version.V_8_0_0; + // TODO add support for other roles? If so, it may have to be an instance method... + // NOTE, whatever determines allocation should not be dynamically set on the node + // Otherwise allocation logic might fail + public static boolean mayAllocateToNode(DiscoveryNode node) { + return node.getRoles().contains(DiscoveryNodeRole.ML_ROLE); + } + public static final Version VERSION_INTRODUCED = Version.V_8_0_0; private static final ParseField MODEL_BYTES = new ParseField("model_bytes"); + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "trained_model_deployment_params", + true, + a -> new TaskParams((String)a[0], (String)a[1], (Long)a[2]) + ); + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), TrainedModelConfig.MODEL_ID); + PARSER.declareString(ConstructingObjectParser.constructorArg(), IndexLocation.INDEX); + PARSER.declareLong(ConstructingObjectParser.constructorArg(), MODEL_BYTES); + } + + public static TaskParams fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } /** * This has been found to be approximately 300MB on linux by manual testing. diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateAction.java new file mode 100644 index 0000000000000..e1ae0e6c2258c --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateAction.java @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReason; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; + +import java.io.IOException; +import java.util.Objects; + +public class UpdateTrainedModelAllocationStateAction extends ActionType { + public static final UpdateTrainedModelAllocationStateAction INSTANCE = new UpdateTrainedModelAllocationStateAction(); + public static final String NAME = "cluster:internal/xpack/ml/model_allocation/update"; + + private UpdateTrainedModelAllocationStateAction() { + super(NAME, AcknowledgedResponse::readFrom); + } + + public static class Request extends MasterNodeRequest { + private final String nodeId; + private final String modelId; + private final RoutingStateAndReason routingState; + + public Request(String nodeId, String modelId, RoutingStateAndReason routingState) { + this.nodeId = ExceptionsHelper.requireNonNull(nodeId, "node_id"); + this.modelId = ExceptionsHelper.requireNonNull(modelId, "model_id"); + this.routingState = ExceptionsHelper.requireNonNull(routingState, "routing_state"); + } + + public Request(StreamInput in) throws IOException { + super(in); + this.nodeId = in.readString(); + this.modelId = in.readString(); + this.routingState = new RoutingStateAndReason(in); + } + + public String getNodeId() { + return nodeId; + } + + public String getModelId() { + return modelId; + } + + public RoutingStateAndReason getRoutingState() { + return routingState; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(nodeId); + out.writeString(modelId); + routingState.writeTo(out); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return Objects.equals(nodeId, request.nodeId) + && Objects.equals(modelId, request.modelId) + && Objects.equals(routingState, request.routingState); + } + + @Override + public int hashCode() { + return Objects.hash(nodeId, modelId, routingState); + } + + @Override + public String toString() { + return "Request{" + "nodeId='" + nodeId + '\'' + ", modelId='" + modelId + '\'' + ", routingState=" + routingState + '}'; + } + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationState.java new file mode 100644 index 0000000000000..c9ef574d39f8f --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationState.java @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import java.util.Locale; + +public enum AllocationState { + STARTED, + STOPPING; + + public static AllocationState fromString(String value) { + return valueOf(value.toUpperCase(Locale.ROOT)); + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingState.java new file mode 100644 index 0000000000000..865a490cbf64a --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingState.java @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import java.util.Locale; + +public enum RoutingState { + STARTING, + STARTED, + STOPPING, + FAILED, + STOPPED; + + public static RoutingState fromString(String value) { + return valueOf(value.toUpperCase(Locale.ROOT)); + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReason.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReason.java new file mode 100644 index 0000000000000..c6f1ce7d71510 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReason.java @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +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.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ParseField; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; + +import java.io.IOException; +import java.util.Objects; + +public class RoutingStateAndReason implements ToXContentObject, Writeable { + + private static final ParseField REASON = new ParseField("reason"); + private static final ParseField ROUTING_STATE = new ParseField("routing_state"); + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "trained_model_routing_state", + a -> new RoutingStateAndReason(RoutingState.fromString((String) a[0]), (String) a[1]) + ); + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), ROUTING_STATE); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), REASON); + } + + public static RoutingStateAndReason fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + private final String reason; + private final RoutingState state; + + public RoutingStateAndReason(RoutingState state, String reason) { + this.state = ExceptionsHelper.requireNonNull(state, ROUTING_STATE); + this.reason = reason; + } + + public RoutingStateAndReason(StreamInput in) throws IOException { + this.state = in.readEnum(RoutingState.class); + this.reason = in.readOptionalString(); + } + + public String getReason() { + return reason; + } + + public RoutingState getState() { + return state; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeEnum(state); + out.writeOptionalString(reason); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(ROUTING_STATE.getPreferredName(), state); + if (reason != null) { + builder.field(REASON.getPreferredName(), reason); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RoutingStateAndReason that = (RoutingStateAndReason) o; + return Objects.equals(reason, that.reason) && state == that.state; + } + + @Override + public int hashCode() { + return Objects.hash(reason, state); + } + + @Override + public String toString() { + return "RoutingStateAndReason{" + "reason='" + reason + '\'' + ", state=" + state + '}'; + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocation.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocation.java new file mode 100644 index 0000000000000..f15511097d8d0 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocation.java @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import org.elasticsearch.ResourceAlreadyExistsException; +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.cluster.AbstractDiffable; +import org.elasticsearch.cluster.Diffable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ParseField; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +// TODO implement better diffable logic so that whole diff does not need to be serialized if only one part changes +/** + * Trained model allocation object that contains allocation options and the allocation routing table + */ +public class TrainedModelAllocation extends AbstractDiffable + implements + Diffable, + ToXContentObject { + + private static final ParseField ALLOCATION_STATE = new ParseField("allocation_state"); + private static final ParseField ROUTING_TABLE = new ParseField("routing_table"); + private static final ParseField TASK_PARAMETERS = new ParseField("task_parameters"); + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "trained_model_allocation", + true, + a -> new TrainedModelAllocation( + (StartTrainedModelDeploymentAction.TaskParams) a[0], + (Map) a[1], + AllocationState.fromString((String)a[2]) + ) + ); + static { + PARSER.declareObject( + ConstructingObjectParser.constructorArg(), + (p, c) -> StartTrainedModelDeploymentAction.TaskParams.fromXContent(p), + TASK_PARAMETERS + ); + PARSER.declareObject( + ConstructingObjectParser.constructorArg(), + (p, c) -> p.map(LinkedHashMap::new, RoutingStateAndReason::fromXContent), + ROUTING_TABLE + ); + PARSER.declareString(ConstructingObjectParser.constructorArg(), ALLOCATION_STATE); + } + + private final StartTrainedModelDeploymentAction.TaskParams taskParams; + private final Map nodeRoutingTable; + private final AllocationState allocationState; + + public static TrainedModelAllocation fromXContent(XContentParser parser) throws IOException { + return PARSER.apply(parser, null); + } + + TrainedModelAllocation( + StartTrainedModelDeploymentAction.TaskParams taskParams, + Map nodeRoutingTable, + AllocationState allocationState + ) { + this.taskParams = ExceptionsHelper.requireNonNull(taskParams, TASK_PARAMETERS); + this.nodeRoutingTable = ExceptionsHelper.requireNonNull(nodeRoutingTable, ROUTING_TABLE); + this.allocationState = ExceptionsHelper.requireNonNull(allocationState, ALLOCATION_STATE); + } + + public TrainedModelAllocation(StreamInput in) throws IOException { + this.taskParams = new StartTrainedModelDeploymentAction.TaskParams(in); + this.nodeRoutingTable = in.readOrderedMap(StreamInput::readString, RoutingStateAndReason::new); + this.allocationState = in.readEnum(AllocationState.class); + } + + public boolean isRoutedToNode(String nodeId) { + return nodeRoutingTable.containsKey(nodeId); + } + + public Map getNodeRoutingTable() { + return Collections.unmodifiableMap(nodeRoutingTable); + } + + public StartTrainedModelDeploymentAction.TaskParams getTaskParams() { + return taskParams; + } + + public AllocationState getAllocationState() { + return allocationState; + } + + public String[] getStartedNodes() { + return nodeRoutingTable + .entrySet() + .stream() + .filter(entry -> RoutingState.STARTED.equals(entry.getValue().getState())) + .map(Map.Entry::getKey) + .toArray(String[]::new); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrainedModelAllocation that = (TrainedModelAllocation) o; + return Objects.equals(nodeRoutingTable, that.nodeRoutingTable) + && Objects.equals(taskParams, that.taskParams) + && Objects.equals(allocationState, that.allocationState); + } + + @Override + public int hashCode() { + return Objects.hash(nodeRoutingTable, taskParams, allocationState); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(TASK_PARAMETERS.getPreferredName(), taskParams); + builder.field(ROUTING_TABLE.getPreferredName(), nodeRoutingTable); + builder.field(ALLOCATION_STATE.getPreferredName(), allocationState); + builder.endObject(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + taskParams.writeTo(out); + out.writeMap(nodeRoutingTable, StreamOutput::writeString, (o, w) -> w.writeTo(o)); + out.writeEnum(allocationState); + } + + public static class Builder { + private final Map nodeRoutingTable; + private final StartTrainedModelDeploymentAction.TaskParams taskParams; + private AllocationState allocationState; + private boolean isChanged; + + public static Builder fromAllocation(TrainedModelAllocation allocation) { + return new Builder(allocation.taskParams, allocation.nodeRoutingTable, allocation.allocationState); + } + + public static Builder empty(StartTrainedModelDeploymentAction.TaskParams taskParams) { + return new Builder(taskParams); + } + + private Builder( + StartTrainedModelDeploymentAction.TaskParams taskParams, + Map nodeRoutingTable, + AllocationState allocationState + ) { + this.taskParams = taskParams; + this.nodeRoutingTable = new LinkedHashMap<>(nodeRoutingTable); + this.allocationState = allocationState; + } + + private Builder(StartTrainedModelDeploymentAction.TaskParams taskParams) { + this.nodeRoutingTable = new LinkedHashMap<>(); + this.taskParams = taskParams; + this.allocationState = AllocationState.STARTED; + } + + public Builder addNewRoutingEntry(String nodeId) { + if (nodeRoutingTable.containsKey(nodeId)) { + throw new ResourceAlreadyExistsException( + "routing entry for node [{}] for model [{}] already exists", nodeId, taskParams.getModelId() + ); + } + isChanged = true; + nodeRoutingTable.put(nodeId, new RoutingStateAndReason(RoutingState.STARTING, "")); + return this; + } + + public Builder addNewFailedRoutingEntry(String nodeId, String reason) { + if (nodeRoutingTable.containsKey(nodeId)) { + throw new ResourceAlreadyExistsException( + "routing entry for node [{}] for model [{}] already exists", nodeId, taskParams.getModelId() + ); + } + isChanged = true; + nodeRoutingTable.put(nodeId, new RoutingStateAndReason(RoutingState.FAILED, reason)); + return this; + } + + public Builder updateExistingRoutingEntry(String nodeId, RoutingStateAndReason state) { + RoutingStateAndReason stateAndReason = nodeRoutingTable.get(nodeId); + if (stateAndReason == null) { + throw new ResourceNotFoundException( + "routing entry for node [{}] for model [{}] does not exist", nodeId, taskParams.getModelId() + ); + } + if (stateAndReason.equals(state)) { + return this; + } + nodeRoutingTable.put(nodeId, state); + isChanged = true; + return this; + } + + public Builder removeRoutingEntry(String nodeId) { + if (nodeRoutingTable.remove(nodeId) != null) { + isChanged = true; + } + return this; + } + + public Builder stopAllocation() { + if (allocationState.equals(AllocationState.STOPPING)) { + return this; + } + isChanged = true; + allocationState = AllocationState.STOPPING; + return this; + } + + public boolean isChanged() { + return isChanged; + } + + public TrainedModelAllocation build() { + return new TrainedModelAllocation(taskParams, nodeRoutingTable, allocationState); + } + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/trainedmodel/IndexLocation.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/trainedmodel/IndexLocation.java index 98be0e92e85cd..85acf4df9d78a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/trainedmodel/IndexLocation.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/trainedmodel/IndexLocation.java @@ -47,7 +47,7 @@ public static IndexLocation fromXContentLenient(XContentParser parser) throws IO private final String modelId; private final String indexName; - IndexLocation(String modelId, String indexName) { + public IndexLocation(String modelId, String indexName) { this.modelId = Objects.requireNonNull(modelId); this.indexName = Objects.requireNonNull(indexName); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionRequestTests.java new file mode 100644 index 0000000000000..ae130f1288653 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionRequestTests.java @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction.Request; + +public class CreateTrainedModelAllocationActionRequestTests extends AbstractWireSerializingTestCase { + + @Override + protected Request createTestInstance() { + return new Request( + new StartTrainedModelDeploymentAction.TaskParams( + randomAlphaOfLength(10), + randomAlphaOfLength(10), + randomNonNegativeLong() + ) + ); + } + + @Override + protected Writeable.Reader instanceReader() { + return Request::new; + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionResponseTests.java new file mode 100644 index 0000000000000..980f5ef050559 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/CreateTrainedModelAllocationActionResponseTests.java @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction.Response; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocationTests; + +import java.io.IOException; + +public class CreateTrainedModelAllocationActionResponseTests extends AbstractSerializingTestCase { + + @Override + protected Response createTestInstance() { + return new Response(TrainedModelAllocationTests.randomInstance()); + } + + @Override + protected Writeable.Reader instanceReader() { + return Response::new; + } + + @Override + protected Response doParseInstance(XContentParser parser) throws IOException { + return Response.fromXContent(parser); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationActionRequestTests.java new file mode 100644 index 0000000000000..933c60dcbf419 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAllocationActionRequestTests.java @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAllocationAction.Request; + +public class DeleteTrainedModelAllocationActionRequestTests extends AbstractWireSerializingTestCase { + + @Override + protected Request createTestInstance() { + return new Request(randomAlphaOfLength(10)); + } + + @Override + protected Writeable.Reader instanceReader() { + return Request::new; + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateActionRequestTests.java new file mode 100644 index 0000000000000..5b7104934f121 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/UpdateTrainedModelAllocationStateActionRequestTests.java @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.core.ml.action; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction.Request; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReasonTests; + +public class UpdateTrainedModelAllocationStateActionRequestTests extends AbstractWireSerializingTestCase { + + @Override + protected Request createTestInstance() { + return new Request(randomAlphaOfLength(10), randomAlphaOfLength(10), RoutingStateAndReasonTests.randomInstance()); + } + + @Override + protected Writeable.Reader instanceReader() { + return Request::new; + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationStateTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationStateTests.java new file mode 100644 index 0000000000000..42a62e35fe80d --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/AllocationStateTests.java @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.equalTo; + +public class AllocationStateTests extends ESTestCase { + + public void testToAndFromString() { + for (AllocationState state : AllocationState.values()) { + String value = state.toString(); + assertThat(AllocationState.fromString(value), equalTo(state)); + } + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReasonTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReasonTests.java new file mode 100644 index 0000000000000..438372248cee3 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateAndReasonTests.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; + +import java.io.IOException; + +public class RoutingStateAndReasonTests extends AbstractSerializingTestCase { + + public static RoutingStateAndReason randomInstance() { + return new RoutingStateAndReason(randomFrom(RoutingState.values()), randomBoolean() ? null : randomAlphaOfLength(10)); + } + + @Override + protected RoutingStateAndReason doParseInstance(XContentParser parser) throws IOException { + return RoutingStateAndReason.fromXContent(parser); + } + + @Override + protected Writeable.Reader instanceReader() { + return RoutingStateAndReason::new; + } + + @Override + protected RoutingStateAndReason createTestInstance() { + return randomInstance(); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateTests.java new file mode 100644 index 0000000000000..883339250ce24 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/RoutingStateTests.java @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.equalTo; + +public class RoutingStateTests extends ESTestCase { + + public void testToAndFromString() { + for (RoutingState state : RoutingState.values()) { + String value = state.toString(); + assertThat(RoutingState.fromString(value), equalTo(state)); + } + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocationTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocationTests.java new file mode 100644 index 0000000000000..903c897bfd24c --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/allocation/TrainedModelAllocationTests.java @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.ml.inference.allocation; + +import org.elasticsearch.ResourceAlreadyExistsException; +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; + +import java.io.IOException; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.is; + +public class TrainedModelAllocationTests extends AbstractSerializingTestCase { + + public static TrainedModelAllocation randomInstance() { + TrainedModelAllocation.Builder builder = TrainedModelAllocation.Builder.empty( + new StartTrainedModelDeploymentAction.TaskParams(randomAlphaOfLength(10), randomAlphaOfLength(10), randomNonNegativeLong()) + ); + List nodes = Stream.generate(() -> randomAlphaOfLength(10)).limit(randomInt(5)).collect(Collectors.toList()); + for (String node : nodes) { + if (randomBoolean()) { + builder.addNewFailedRoutingEntry(node, randomAlphaOfLength(10)); + } else { + builder.addNewRoutingEntry(node); + } + } + return builder.build(); + } + + @Override + protected TrainedModelAllocation doParseInstance(XContentParser parser) throws IOException { + return TrainedModelAllocation.fromXContent(parser); + } + + @Override + protected Writeable.Reader instanceReader() { + return TrainedModelAllocation::new; + } + + @Override + protected TrainedModelAllocation createTestInstance() { + return randomInstance(); + } + + public void testBuilderChanged() { + TrainedModelAllocation original = randomInstance(); + TrainedModelAllocation.Builder builder = TrainedModelAllocation.Builder.fromAllocation(original); + assertThat(builder.isChanged(), is(false)); + String addingNode = "foo"; + + assertUnchanged(builder, b -> b.removeRoutingEntry(addingNode)); + + if (randomBoolean()) { + builder.addNewRoutingEntry(addingNode); + } else { + builder.addNewFailedRoutingEntry(addingNode, "test failed"); + } + assertThat(builder.isChanged(), is(true)); + + TrainedModelAllocation.Builder builderWithNode = TrainedModelAllocation.Builder.fromAllocation(builder.build()); + assertThat(builderWithNode.isChanged(), is(false)); + + builderWithNode.removeRoutingEntry(addingNode); + assertThat(builderWithNode.isChanged(), is(true)); + } + + public void testBuilderAddingExistingRoute() { + TrainedModelAllocation original = randomInstance(); + TrainedModelAllocation.Builder builder = TrainedModelAllocation.Builder.fromAllocation(original); + String addingNode = "new-node"; + if (randomBoolean()) { + builder.addNewRoutingEntry(addingNode); + } else { + builder.addNewFailedRoutingEntry(addingNode, "test failed"); + } + expectThrows(ResourceAlreadyExistsException.class, () -> builder.addNewFailedRoutingEntry("new-node", "anything")); + expectThrows(ResourceAlreadyExistsException.class, () -> builder.addNewRoutingEntry("new-node")); + } + + public void testBuilderUpdatingMissingRoute() { + TrainedModelAllocation original = randomInstance(); + TrainedModelAllocation.Builder builder = TrainedModelAllocation.Builder.fromAllocation(original); + String addingNode = "new-node"; + expectThrows( + ResourceNotFoundException.class, + () -> builder.updateExistingRoutingEntry(addingNode, RoutingStateAndReasonTests.randomInstance()) + ); + } + + public void testGetStartedNodes() { + String startedNode1 = "started-node-1"; + String startedNode2 = "started-node-2"; + String nodeInAnotherState1 = "another-state-node-1"; + String nodeInAnotherState2 = "another-state-node-2"; + TrainedModelAllocation allocation = TrainedModelAllocation.Builder.empty( + new StartTrainedModelDeploymentAction.TaskParams(randomAlphaOfLength(10), randomAlphaOfLength(10), randomNonNegativeLong()) + ) + .addNewRoutingEntry(startedNode1) + .addNewRoutingEntry(startedNode2) + .addNewRoutingEntry(nodeInAnotherState1) + .addNewRoutingEntry(nodeInAnotherState2) + .updateExistingRoutingEntry(startedNode1, new RoutingStateAndReason(RoutingState.STARTED, "")) + .updateExistingRoutingEntry(startedNode2, new RoutingStateAndReason(RoutingState.STARTED, "")) + .updateExistingRoutingEntry( + nodeInAnotherState1, + new RoutingStateAndReason( + randomFrom(RoutingState.STARTING, RoutingState.FAILED, RoutingState.STOPPED, RoutingState.STOPPING), + randomAlphaOfLength(10) + ) + ) + .updateExistingRoutingEntry( + nodeInAnotherState2, + new RoutingStateAndReason( + randomFrom(RoutingState.STARTING, RoutingState.FAILED, RoutingState.STOPPED, RoutingState.STOPPING), + randomAlphaOfLength(10) + ) + ) + .build(); + assertThat(allocation.getStartedNodes(), arrayContainingInAnyOrder(startedNode1, startedNode2)); + } + + private static void assertUnchanged( + TrainedModelAllocation.Builder builder, + Function function + ) { + function.apply(builder); + assertThat(builder.isChanged(), is(false)); + } + +} diff --git a/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/PyTorchModelIT.java b/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/PyTorchModelIT.java index a3ed3d0c51634..5b366c7e83b38 100644 --- a/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/PyTorchModelIT.java +++ b/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/PyTorchModelIT.java @@ -15,6 +15,8 @@ import org.elasticsearch.test.SecuritySettingsSourceField; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; +import org.junit.After; +import org.junit.Before; import java.io.IOException; import java.util.Base64; @@ -53,6 +55,32 @@ protected Settings restClientSettings() { return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE_SUPER_USER).build(); } + @Before + public void setLogging() throws IOException { + Request loggingSettings = new Request("PUT", "_cluster/settings"); + loggingSettings.setJsonEntity("" + + "{" + + "\"transient\" : {\n" + + " \"logger.org.elasticsearch.xpack.ml.inference.allocation\" : \"TRACE\",\n" + + " \"logger.org.elasticsearch.xpack.ml.inference.deployment\" : \"TRACE\"\n" + + " }" + + "}"); + client().performRequest(loggingSettings); + } + + @After + public void unsetLogging() throws IOException { + Request loggingSettings = new Request("PUT", "_cluster/settings"); + loggingSettings.setJsonEntity("" + + "{" + + "\"transient\" : {\n" + + " \"logger.org.elasticsearch.xpack.ml.inference.allocation\" :null,\n" + + " \"logger.org.elasticsearch.xpack.ml.inference.deployment\" : null\n" + + " }" + + "}"); + client().performRequest(loggingSettings); + } + private static final String MODEL_INDEX = "model_store"; private static final String MODEL_ID ="simple_model_to_evaluate"; private static final String BASE_64_ENCODED_MODEL = @@ -92,8 +120,11 @@ public void testEvaluate() throws IOException { createTrainedModel(); startDeployment(); try { - Response inference = infer("my words"); - assertThat(EntityUtils.toString(inference.getEntity()), equalTo("{\"inference\":[[1.0,1.0]]}")); + // Adding multiple inference calls to verify different calls get routed to separate nodes + for (int i = 0; i < 10; i++) { + Response inference = infer("my words"); + assertThat(EntityUtils.toString(inference.getEntity()), equalTo("{\"inference\":[[1.0,1.0]]}")); + } } finally { stopDeployment(); } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java index 06439fb04b107..1c4c3572e992c 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java @@ -89,6 +89,7 @@ import org.elasticsearch.xpack.core.ml.MlStatsIndex; import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.CloseJobAction; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction; import org.elasticsearch.xpack.core.ml.action.DeleteCalendarAction; import org.elasticsearch.xpack.core.ml.action.DeleteCalendarEventAction; import org.elasticsearch.xpack.core.ml.action.DeleteDataFrameAnalyticsAction; @@ -100,6 +101,7 @@ import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction; import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAction; import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction; +import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAllocationAction; import org.elasticsearch.xpack.core.ml.action.GetDatafeedRunningStateAction; import org.elasticsearch.xpack.core.ml.action.InferTrainedModelDeploymentAction; import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; @@ -159,6 +161,7 @@ import org.elasticsearch.xpack.core.ml.action.UpdateJobAction; import org.elasticsearch.xpack.core.ml.action.UpdateModelSnapshotAction; import org.elasticsearch.xpack.core.ml.action.UpdateProcessAction; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; import org.elasticsearch.xpack.core.ml.action.UpgradeJobModelSnapshotAction; import org.elasticsearch.xpack.core.ml.action.ValidateDetectorAction; import org.elasticsearch.xpack.core.ml.action.ValidateJobConfigAction; @@ -177,6 +180,7 @@ import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; import org.elasticsearch.xpack.core.template.TemplateUtils; import org.elasticsearch.xpack.ml.action.TransportCloseJobAction; +import org.elasticsearch.xpack.ml.action.TransportCreateTrainedModelAllocationAction; import org.elasticsearch.xpack.ml.action.TransportDeleteCalendarAction; import org.elasticsearch.xpack.ml.action.TransportDeleteCalendarEventAction; import org.elasticsearch.xpack.ml.action.TransportDeleteDataFrameAnalyticsAction; @@ -188,6 +192,7 @@ import org.elasticsearch.xpack.ml.action.TransportDeleteModelSnapshotAction; import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAction; import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAliasAction; +import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAllocationAction; import org.elasticsearch.xpack.ml.action.TransportGetDatafeedRunningStateAction; import org.elasticsearch.xpack.ml.action.TransportInferTrainedModelDeploymentAction; import org.elasticsearch.xpack.ml.action.TransportStartTrainedModelDeploymentAction; @@ -247,6 +252,7 @@ import org.elasticsearch.xpack.ml.action.TransportUpdateJobAction; import org.elasticsearch.xpack.ml.action.TransportUpdateModelSnapshotAction; import org.elasticsearch.xpack.ml.action.TransportUpdateProcessAction; +import org.elasticsearch.xpack.ml.action.TransportUpdateTrainedModelAllocationStateAction; import org.elasticsearch.xpack.ml.action.TransportUpgradeJobModelSnapshotAction; import org.elasticsearch.xpack.ml.action.TransportValidateDetectorAction; import org.elasticsearch.xpack.ml.action.TransportValidateJobConfigAction; @@ -275,6 +281,9 @@ import org.elasticsearch.xpack.ml.dataframe.process.results.MemoryUsageEstimationResult; import org.elasticsearch.xpack.ml.inference.ModelAliasMetadata; import org.elasticsearch.xpack.ml.inference.TrainedModelStatsService; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationClusterService; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationMetadata; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationService; import org.elasticsearch.xpack.ml.inference.deployment.DeploymentManager; import org.elasticsearch.xpack.ml.inference.ingest.InferenceProcessor; import org.elasticsearch.xpack.ml.inference.loadingservice.ModelLoadingService; @@ -284,6 +293,7 @@ import org.elasticsearch.xpack.ml.inference.pytorch.process.PyTorchProcessFactory; import org.elasticsearch.xpack.ml.job.JobManager; import org.elasticsearch.xpack.ml.job.JobManagerHolder; +import org.elasticsearch.xpack.ml.job.NodeLoadDetector; import org.elasticsearch.xpack.ml.job.UpdateJobProcessNotifier; import org.elasticsearch.xpack.ml.job.categorization.FirstNonBlankLineCharFilter; import org.elasticsearch.xpack.ml.job.categorization.FirstNonBlankLineCharFilterFactory; @@ -855,6 +865,18 @@ public Collection createComponents(Client client, ClusterService cluster // Perform node startup operations nativeStorageProvider.cleanupLocalTmpStorageInCaseOfUncleanShutdown(); + // allocation service objects + final TrainedModelAllocationService trainedModelAllocationService = new TrainedModelAllocationService( + client, + clusterService, + threadPool + ); + final TrainedModelAllocationClusterService trainedModelAllocationClusterService = new TrainedModelAllocationClusterService( + settings, + clusterService, + new NodeLoadDetector(memoryTracker) + ); + mlAutoscalingDeciderService.set(new MlAutoscalingDeciderService(memoryTracker, settings, clusterService)); return Arrays.asList( @@ -882,7 +904,10 @@ public Collection createComponents(Client client, ClusterService cluster dataFrameAnalyticsConfigProvider, nativeStorageProvider, modelLoadingService, - trainedModelProvider + trainedModelProvider, + trainedModelAllocationService, + trainedModelAllocationClusterService, + deploymentManager.get() ); } @@ -917,12 +942,7 @@ public List> getPersistentTasksExecutor(ClusterServic autodetectProcessManager.get(), memoryTracker.get(), expressionResolver, - client), - new TransportStartTrainedModelDeploymentAction.TaskExecutor(settings, - clusterService, - expressionResolver, - memoryTracker.get(), - deploymentManager.get()) + client) ); } @@ -1094,6 +1114,12 @@ public List getRestHandlers(Settings settings, RestController restC new ActionHandler<>(StopTrainedModelDeploymentAction.INSTANCE, TransportStopTrainedModelDeploymentAction.class), new ActionHandler<>(InferTrainedModelDeploymentAction.INSTANCE, TransportInferTrainedModelDeploymentAction.class), new ActionHandler<>(GetDatafeedRunningStateAction.INSTANCE, TransportGetDatafeedRunningStateAction.class), + new ActionHandler<>(CreateTrainedModelAllocationAction.INSTANCE, TransportCreateTrainedModelAllocationAction.class), + new ActionHandler<>(DeleteTrainedModelAllocationAction.INSTANCE, TransportDeleteTrainedModelAllocationAction.class), + new ActionHandler<>( + UpdateTrainedModelAllocationStateAction.INSTANCE, + TransportUpdateTrainedModelAllocationStateAction.class + ), usageAction, infoAction); } @@ -1217,6 +1243,13 @@ public List getNamedXContent() { ModelAliasMetadata::fromXContent ) ); + namedXContent.add( + new NamedXContentRegistry.Entry( + Metadata.Custom.class, + new ParseField(TrainedModelAllocationMetadata.NAME), + TrainedModelAllocationMetadata::fromXContent + ) + ); namedXContent.addAll(new CorrelationNamedContentProvider().getNamedXContentParsers()); return namedXContent; } @@ -1230,6 +1263,20 @@ public List getNamedWriteables() { namedWriteables.add(new NamedWriteableRegistry.Entry(NamedDiff.class, "ml", MlMetadata.MlMetadataDiff::new)); namedWriteables.add(new NamedWriteableRegistry.Entry(Metadata.Custom.class, ModelAliasMetadata.NAME, ModelAliasMetadata::new)); namedWriteables.add(new NamedWriteableRegistry.Entry(NamedDiff.class, ModelAliasMetadata.NAME, ModelAliasMetadata::readDiffFrom)); + namedWriteables.add( + new NamedWriteableRegistry.Entry( + Metadata.Custom.class, + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata::new + ) + ); + namedWriteables.add( + new NamedWriteableRegistry.Entry( + NamedDiff.class, + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata::readDiffFrom + ) + ); // Persistent tasks params namedWriteables.add(new NamedWriteableRegistry.Entry(PersistentTaskParams.class, MlTasks.DATAFEED_TASK_NAME, diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportCreateTrainedModelAllocationAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportCreateTrainedModelAllocationAction.java new file mode 100644 index 0000000000000..45861de90a87f --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportCreateTrainedModelAllocationAction.java @@ -0,0 +1,82 @@ +/* + * 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.ml.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction.Request; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction.Response; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationClusterService; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationNodeService; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationService; +import org.elasticsearch.xpack.ml.inference.deployment.DeploymentManager; + +public class TransportCreateTrainedModelAllocationAction extends TransportMasterNodeAction { + + private final TrainedModelAllocationClusterService trainedModelAllocationClusterService; + + @Inject + public TransportCreateTrainedModelAllocationAction( + TrainedModelAllocationClusterService trainedModelAllocationClusterService, + TrainedModelAllocationService trainedModelAllocationService, + DeploymentManager deploymentManager, + TransportService transportService, + ClusterService clusterService, + ThreadPool threadPool, + ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver + ) { + super( + CreateTrainedModelAllocationAction.NAME, + false, + transportService, + clusterService, + threadPool, + actionFilters, + Request::new, + indexNameExpressionResolver, + Response::new, + ThreadPool.Names.SAME + ); + this.trainedModelAllocationClusterService = trainedModelAllocationClusterService; + // Here we create our singleton for the node service + clusterService.addListener( + new TrainedModelAllocationNodeService( + trainedModelAllocationService, + clusterService, + deploymentManager, + transportService.getTaskManager(), + threadPool + ) + ); + } + + @Override + protected void masterOperation(Task task, Request request, ClusterState state, ActionListener listener) throws Exception { + trainedModelAllocationClusterService.createNewModelAllocation( + request.getTaskParams(), + ActionListener.wrap(trainedModelAllocation -> listener.onResponse(new Response(trainedModelAllocation)), listener::onFailure) + ); + } + + @Override + protected ClusterBlockException checkBlock(Request request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAllocationAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAllocationAction.java new file mode 100644 index 0000000000000..d1c1cd0e42a69 --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAllocationAction.java @@ -0,0 +1,64 @@ +/* + * 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.ml.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAllocationAction; +import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAllocationAction.Request; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationClusterService; + +public class TransportDeleteTrainedModelAllocationAction extends AcknowledgedTransportMasterNodeAction { + + private final TrainedModelAllocationClusterService trainedModelAllocationClusterService; + + @Inject + public TransportDeleteTrainedModelAllocationAction( + TrainedModelAllocationClusterService trainedModelAllocationClusterService, + TransportService transportService, + ClusterService clusterService, + ThreadPool threadPool, + ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver + ) { + super( + DeleteTrainedModelAllocationAction.NAME, + false, + transportService, + clusterService, + threadPool, + actionFilters, + Request::new, + indexNameExpressionResolver, + ThreadPool.Names.SAME + ); + this.trainedModelAllocationClusterService = trainedModelAllocationClusterService; + } + + @Override + protected void masterOperation(Task task, Request request, ClusterState state, ActionListener listener) + throws Exception { + trainedModelAllocationClusterService.removeModelAllocation(request.getModelId(), listener); + } + + @Override + protected ClusterBlockException checkBlock(Request request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportInferTrainedModelDeploymentAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportInferTrainedModelDeploymentAction.java index be5a218985a13..a9f0d409c2d08 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportInferTrainedModelDeploymentAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportInferTrainedModelDeploymentAction.java @@ -13,14 +13,15 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.tasks.TransportTasksAction; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Randomness; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.persistent.PersistentTasksCustomMetadata; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.InferTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationMetadata; import org.elasticsearch.xpack.ml.inference.deployment.TrainedModelDeploymentTask; import java.util.List; @@ -42,15 +43,24 @@ protected void doExecute(Task task, InferTrainedModelDeploymentAction.Request re String deploymentId = request.getDeploymentId(); // We need to check whether there is at least an assigned task here, otherwise we cannot redirect to the // node running the job task. - PersistentTasksCustomMetadata tasks = clusterService.state().getMetadata().custom(PersistentTasksCustomMetadata.TYPE); - PersistentTasksCustomMetadata.PersistentTask deploymentTask = MlTasks.getTrainedModelDeploymentTask(deploymentId, tasks); - if (deploymentTask == null || deploymentTask.isAssigned() == false) { + TrainedModelAllocation allocation = TrainedModelAllocationMetadata + .allocationForModelId(clusterService.state(), request.getDeploymentId()) + .orElse(null); + if (allocation == null) { String message = "Cannot perform requested action because deployment [" + deploymentId + "] is not started"; listener.onFailure(ExceptionsHelper.conflictStatusException(message)); - } else { - request.setNodes(deploymentTask.getExecutorNode()); - super.doExecute(task, request, listener); + return; + } + String[] randomRunningNode = allocation.getStartedNodes(); + if (randomRunningNode.length == 0) { + String message = "Cannot perform requested action because deployment [" + deploymentId + "] is not yet running on any node"; + listener.onFailure(ExceptionsHelper.conflictStatusException(message)); + return; } + // TODO Do better routing for inference calls + int nodeIndex = Randomness.get().nextInt(randomRunningNode.length); + request.setNodes(randomRunningNode[nodeIndex]); + super.doExecute(task, request, listener); } @Override diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartTrainedModelDeploymentAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartTrainedModelDeploymentAction.java index 270f886d3cb8c..cf7bd0ecdeac2 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartTrainedModelDeploymentAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartTrainedModelDeploymentAction.java @@ -20,58 +20,49 @@ import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.core.TimeValue; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.persistent.AllocatedPersistentTask; -import org.elasticsearch.persistent.PersistentTaskParams; -import org.elasticsearch.persistent.PersistentTaskState; import org.elasticsearch.persistent.PersistentTasksCustomMetadata; -import org.elasticsearch.persistent.PersistentTasksService; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.tasks.Task; -import org.elasticsearch.tasks.TaskId; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.XPackField; -import org.elasticsearch.xpack.core.ml.MlTasks; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction; import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsAction; -import org.elasticsearch.xpack.core.ml.action.NodeAcknowledgedResponse; import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction.TaskParams; import org.elasticsearch.xpack.core.ml.inference.TrainedModelConfig; import org.elasticsearch.xpack.core.ml.inference.TrainedModelType; -import org.elasticsearch.xpack.core.ml.inference.deployment.TrainedModelDeploymentState; -import org.elasticsearch.xpack.core.ml.inference.deployment.TrainedModelDeploymentTaskState; -import org.elasticsearch.xpack.core.ml.inference.persistence.InferenceIndexConstants; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingState; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReason; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; import org.elasticsearch.xpack.ml.MachineLearning; -import org.elasticsearch.xpack.ml.inference.deployment.DeploymentManager; -import org.elasticsearch.xpack.ml.inference.deployment.TrainedModelDeploymentTask; import org.elasticsearch.xpack.ml.inference.persistence.ChunkedTrainedModelRestorer; -import org.elasticsearch.xpack.ml.job.JobNodeSelector; import org.elasticsearch.xpack.ml.process.MlMemoryTracker; -import org.elasticsearch.xpack.ml.task.AbstractJobPersistentTasksExecutor; -import java.util.Collection; import java.util.Map; import java.util.Objects; -import java.util.Optional; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationService; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import java.util.function.Predicate; public class TransportStartTrainedModelDeploymentAction - extends TransportMasterNodeAction { + extends TransportMasterNodeAction { private static final Logger logger = LogManager.getLogger(TransportStartTrainedModelDeploymentAction.class); private final XPackLicenseState licenseState; private final Client client; - private final PersistentTasksService persistentTasksService; + private final TrainedModelAllocationService trainedModelAllocationService; private final NamedXContentRegistry xContentRegistry; private final MlMemoryTracker memoryTracker; @@ -79,31 +70,32 @@ public class TransportStartTrainedModelDeploymentAction public TransportStartTrainedModelDeploymentAction(TransportService transportService, Client client, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, XPackLicenseState licenseState, IndexNameExpressionResolver indexNameExpressionResolver, - PersistentTasksService persistentTasksService, + TrainedModelAllocationService trainedModelAllocationService, NamedXContentRegistry xContentRegistry, MlMemoryTracker memoryTracker) { super(StartTrainedModelDeploymentAction.NAME, transportService, clusterService, threadPool, actionFilters, - StartTrainedModelDeploymentAction.Request::new, indexNameExpressionResolver, NodeAcknowledgedResponse::new, + StartTrainedModelDeploymentAction.Request::new, indexNameExpressionResolver, CreateTrainedModelAllocationAction.Response::new, ThreadPool.Names.SAME); this.licenseState = Objects.requireNonNull(licenseState); this.client = Objects.requireNonNull(client); - this.persistentTasksService = Objects.requireNonNull(persistentTasksService); this.xContentRegistry = Objects.requireNonNull(xContentRegistry); this.memoryTracker = Objects.requireNonNull(memoryTracker); + this.trainedModelAllocationService = Objects.requireNonNull(trainedModelAllocationService); } @Override protected void masterOperation(Task task, StartTrainedModelDeploymentAction.Request request, ClusterState state, - ActionListener listener) throws Exception { - logger.debug(() -> new ParameterizedMessage("[{}] received deploy request", request.getModelId())); + ActionListener listener) throws Exception { + logger.trace(() -> new ParameterizedMessage("[{}] received deploy request", request.getModelId())); if (licenseState.checkFeature(XPackLicenseState.Feature.MACHINE_LEARNING) == false) { listener.onFailure(LicenseUtils.newComplianceException(XPackField.MACHINE_LEARNING)); return; } - ActionListener> waitForDeploymentToStart = + ActionListener waitForDeploymentToStart = ActionListener.wrap( - startedTask -> waitForDeploymentStarted(startedTask, request.getTimeout(), listener), + modelAllocation -> waitForDeploymentStarted(request.getModelId(), request.getTimeout(), listener), e -> { + logger.warn(() -> new ParameterizedMessage("[{}] creating new allocation failed", request.getModelId()), e); if (ExceptionsHelper.unwrapCause(e) instanceof ResourceAlreadyExistsException) { e = new ElasticsearchStatusException( "Cannot start deployment [{}] because it has already been started", @@ -150,9 +142,7 @@ protected void masterOperation(Task task, StartTrainedModelDeploymentAction.Requ PersistentTasksCustomMetadata persistentTasks = clusterService.state().getMetadata().custom( PersistentTasksCustomMetadata.TYPE); memoryTracker.refresh(persistentTasks, ActionListener.wrap( - aVoid -> persistentTasksService.sendStartRequest( - MlTasks.trainedModelDeploymentTaskId(request.getModelId()), - MlTasks.TRAINED_MODEL_DEPLOYMENT_TASK_NAME, + aVoid -> trainedModelAllocationService.createNewModelAllocation( taskParams, waitForDeploymentToStart ), @@ -162,6 +152,7 @@ protected void masterOperation(Task task, StartTrainedModelDeploymentAction.Requ }, listener::onFailure )); + }, listener::onFailure ); @@ -191,17 +182,20 @@ private void getModelBytes(TrainedModelConfig trainedModelConfig, ActionListener ); } - private void waitForDeploymentStarted(PersistentTasksCustomMetadata.PersistentTask task, - TimeValue timeout, ActionListener listener) { - DeploymentStartedPredicate predicate = new DeploymentStartedPredicate(); - persistentTasksService.waitForPersistentTaskCondition(task.getId(), predicate, timeout, - new PersistentTasksService.WaitForPersistentTaskListener() { + private void waitForDeploymentStarted( + String modelId, + TimeValue timeout, + ActionListener listener + ) { + DeploymentStartedPredicate predicate = new DeploymentStartedPredicate(modelId); + trainedModelAllocationService.waitForAllocationCondition(modelId, predicate, timeout, + new TrainedModelAllocationService.WaitForAllocationListener() { @Override - public void onResponse(PersistentTasksCustomMetadata.PersistentTask persistentTask) { + public void onResponse(TrainedModelAllocation allocation) { if (predicate.exception != null) { - cancelFailedDeployment(task, predicate.exception, listener); + deleteFailedDeployment(modelId, predicate.exception, listener); } else { - listener.onResponse(new NodeAcknowledgedResponse(true, predicate.node)); + listener.onResponse(new CreateTrainedModelAllocationAction.Response(allocation)); } } @@ -212,15 +206,20 @@ public void onFailure(Exception e) { }); } - private void cancelFailedDeployment( - PersistentTasksCustomMetadata.PersistentTask persistentTask, Exception exception, - ActionListener listener) { - persistentTasksService.sendRemoveRequest(persistentTask.getId(), ActionListener.wrap( + private void deleteFailedDeployment( + String modelId, + Exception exception, + ActionListener listener + ) { + trainedModelAllocationService.deleteModelAllocation(modelId, ActionListener.wrap( pTask -> listener.onFailure(exception), e -> { logger.error( - new ParameterizedMessage("[{}] Failed to cancel persistent task that had failed with the reason [{}]", - persistentTask.getParams().getModelId(), exception.getMessage()), + new ParameterizedMessage( + "[{}] Failed to delete model allocation that had failed with the reason [{}]", + modelId, + exception.getMessage() + ), e ); listener.onFailure(exception); @@ -237,148 +236,56 @@ protected ClusterBlockException checkBlock(StartTrainedModelDeploymentAction.Req return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); } - private static class DeploymentStartedPredicate implements Predicate> { + private static class DeploymentStartedPredicate implements Predicate { private volatile Exception exception; - private volatile String node = ""; + + // for logging + private final String modelId; + + DeploymentStartedPredicate(String modelId) { + this.modelId = ExceptionsHelper.requireNonNull(modelId, "model_id"); + } @Override - public boolean test(PersistentTasksCustomMetadata.PersistentTask persistentTask) { - if (persistentTask == null) { - return false; + public boolean test(TrainedModelAllocation trainedModelAllocation) { + if (trainedModelAllocation == null) { + // Something weird happened, it should NEVER be null... + return true; } - PersistentTasksCustomMetadata.Assignment assignment = persistentTask.getAssignment(); - - String reason = "__unknown__"; + final Set> nodesAndState = trainedModelAllocation + .getNodeRoutingTable() + .entrySet(); - if (assignment != null) { - if (assignment.equals(JobNodeSelector.AWAITING_LAZY_ASSIGNMENT)) { - return true; + Map nodeFailuresAndReasons = new HashMap<>(); + Set nodesStillInitializing = new HashSet<>(); + for (Map.Entry nodeIdAndState : nodesAndState) { + if (RoutingState.FAILED.equals(nodeIdAndState.getValue().getState())) { + nodeFailuresAndReasons.put(nodeIdAndState.getKey(), nodeIdAndState.getValue().getReason()); } - if (assignment.equals(PersistentTasksCustomMetadata.INITIAL_ASSIGNMENT) == false && assignment.isAssigned() == false) { - exception = new ElasticsearchStatusException("Could not start trained model deployment, allocation explanation [{}]", - RestStatus.TOO_MANY_REQUESTS, assignment.getExplanation()); - return true; + if (RoutingState.STARTING.equals(nodeIdAndState.getValue().getState())) { + nodesStillInitializing.add(nodeIdAndState.getKey()); } } - TrainedModelDeploymentTaskState taskState = (TrainedModelDeploymentTaskState) persistentTask.getState(); - reason = taskState != null ? taskState.getReason() : reason; - TrainedModelDeploymentState deploymentState = taskState == null ? TrainedModelDeploymentState.STARTING : taskState.getState(); - - switch (deploymentState) { - case STARTED: - node = persistentTask.getExecutorNode(); - return true; - case STARTING: - case STOPPING: - case STOPPED: - return false; - case FAILED: - exception = ExceptionsHelper.serverError("Deployment failed with reason: {}", reason); - return true; - default: - exception = ExceptionsHelper.serverError("Unexpected task state [{}] with reason [{}] while waiting to be started", - taskState.getState(), reason); - return true; + if (nodeFailuresAndReasons.isEmpty() == false) { + exception = new ElasticsearchStatusException( + "Could not start trained model deployment, the following nodes failed with errors [{}]", + RestStatus.INTERNAL_SERVER_ERROR, + nodeFailuresAndReasons + ); + return true; } - } - } - - public static class TaskExecutor extends AbstractJobPersistentTasksExecutor { - - private final DeploymentManager manager; - - public TaskExecutor(Settings settings, ClusterService clusterService, IndexNameExpressionResolver expressionResolver, - MlMemoryTracker memoryTracker, DeploymentManager manager) { - super(MlTasks.TRAINED_MODEL_DEPLOYMENT_TASK_NAME, - MachineLearning.UTILITY_THREAD_POOL_NAME, - settings, - clusterService, - memoryTracker, - expressionResolver); - this.manager = Objects.requireNonNull(manager); - } - @Override - protected AllocatedPersistentTask createTask( - long id, String type, String action, TaskId parentTaskId, - PersistentTasksCustomMetadata.PersistentTask persistentTask, - Map headers) { - return new TrainedModelDeploymentTask(id, type, action, parentTaskId, headers, persistentTask.getParams()); - } - - @Override - public PersistentTasksCustomMetadata.Assignment getAssignment(TaskParams params, - Collection candidateNodes, - ClusterState clusterState) { - - boolean isMemoryTrackerRecentlyRefreshed = memoryTracker.isRecentlyRefreshed(); - Optional optionalAssignment = - getPotentialAssignment(params, clusterState, isMemoryTrackerRecentlyRefreshed); - // NOTE: this will return here if isMemoryTrackerRecentlyRefreshed is false, we don't allow assignment with stale memory - if (optionalAssignment.isPresent()) { - return optionalAssignment.get(); + if (nodesStillInitializing.isEmpty()) { + return true; } - - JobNodeSelector jobNodeSelector = - new JobNodeSelector( - clusterState, - candidateNodes, - params.getModelId(), - MlTasks.TRAINED_MODEL_DEPLOYMENT_TASK_NAME, - memoryTracker, - maxLazyMLNodes, - node -> nodeFilter(node, params) - ); - - PersistentTasksCustomMetadata.Assignment assignment = jobNodeSelector.selectNode( - params.estimateMemoryUsageBytes(), - maxOpenJobs, - Integer.MAX_VALUE, - maxMachineMemoryPercent, - maxNodeMemory, - useAutoMemoryPercentage + logger.trace( + () -> new ParameterizedMessage("[{}] tested and nodes {} still initializing", modelId, nodesStillInitializing) ); - return assignment; - } - - public static String nodeFilter(DiscoveryNode node, TaskParams params) { - String id = params.getModelId(); - - if (node.getVersion().before(TaskParams.VERSION_INTRODUCED)) { - return "Not opening job [" + id + "] on node [" + JobNodeSelector.nodeNameAndVersion(node) - + "], because the data frame analytics requires a node of version [" - + TaskParams.VERSION_INTRODUCED + "] or higher"; - } - - return null; - } - - @Override - protected void nodeOperation(AllocatedPersistentTask task, TaskParams params, PersistentTaskState state) { - TrainedModelDeploymentTask trainedModelDeploymentTask = (TrainedModelDeploymentTask) task; - trainedModelDeploymentTask.setDeploymentManager(manager); - - TrainedModelDeploymentTaskState deployingState = new TrainedModelDeploymentTaskState( - TrainedModelDeploymentState.STARTING, task.getAllocationId(), null); - task.updatePersistentTaskState(deployingState, ActionListener.wrap( - response -> manager.startDeployment(trainedModelDeploymentTask), - task::markAsFailed - )); - } - - @Override - protected String[] indicesOfInterest(TaskParams params) { - return new String[] { - InferenceIndexConstants.INDEX_PATTERN - }; - } - - @Override - protected String getJobId(TaskParams params) { - return params.getModelId(); + return false; } } + } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStopTrainedModelDeploymentAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStopTrainedModelDeploymentAction.java index 30b3d663dd5b6..7b8753e421034 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStopTrainedModelDeploymentAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStopTrainedModelDeploymentAction.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListenerResponseHandler; @@ -17,51 +18,59 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.tasks.TransportTasksAction; import org.elasticsearch.client.Client; +import org.elasticsearch.client.OriginSettingClient; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.discovery.MasterNotDiscoveredException; -import org.elasticsearch.persistent.PersistentTasksCustomMetadata; -import org.elasticsearch.persistent.PersistentTasksService; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsAction; import org.elasticsearch.xpack.core.ml.action.StopTrainedModelDeploymentAction; import org.elasticsearch.xpack.core.ml.inference.TrainedModelConfig; -import org.elasticsearch.xpack.core.ml.inference.deployment.TrainedModelDeploymentState; -import org.elasticsearch.xpack.core.ml.inference.deployment.TrainedModelDeploymentTaskState; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; -import org.elasticsearch.xpack.ml.MachineLearning; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationClusterService; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationMetadata; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationService; import org.elasticsearch.xpack.ml.inference.deployment.TrainedModelDeploymentTask; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Set; +import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; + +/** + * Class for transporting stop trained model deloyment requests. + * + * NOTE: this class gets routed to each individual deployment running on the nodes. This way when the request returns, we are assured + * that the model is not running any longer on any node. + */ public class TransportStopTrainedModelDeploymentAction extends TransportTasksAction { private static final Logger logger = LogManager.getLogger(TransportStopTrainedModelDeploymentAction.class); private final Client client; - private final ThreadPool threadPool; - private final PersistentTasksService persistentTasksService; + private final TrainedModelAllocationService trainedModelAllocationService; + private final TrainedModelAllocationClusterService trainedModelAllocationClusterService; @Inject public TransportStopTrainedModelDeploymentAction(ClusterService clusterService, TransportService transportService, - ActionFilters actionFilters, Client client, ThreadPool threadPool, - PersistentTasksService persistentTasksService) { + ActionFilters actionFilters, Client client, + TrainedModelAllocationService trainedModelAllocationService, + TrainedModelAllocationClusterService trainedModelAllocationClusterService) { super(StopTrainedModelDeploymentAction.NAME, clusterService, transportService, actionFilters, StopTrainedModelDeploymentAction.Request::new, StopTrainedModelDeploymentAction.Response::new, StopTrainedModelDeploymentAction.Response::new, ThreadPool.Names.SAME); - this.client = client; - this.threadPool = threadPool; - this.persistentTasksService = persistentTasksService; + this.client = new OriginSettingClient(client, ML_ORIGIN); + this.trainedModelAllocationService = trainedModelAllocationService; + this.trainedModelAllocationClusterService = trainedModelAllocationClusterService; } @Override @@ -69,6 +78,7 @@ protected void doExecute(Task task, StopTrainedModelDeploymentAction.Request req ActionListener listener) { ClusterState state = clusterService.state(); DiscoveryNodes nodes = state.nodes(); + // Master node is required for initial pre-checks and deletion preparation if (nodes.isLocalNodeElectedMaster() == false) { redirectToMasterNode(nodes.getMasterNode(), request, listener); return; @@ -88,15 +98,30 @@ protected void doExecute(Task task, StopTrainedModelDeploymentAction.Request req return; } - ClusterState clusterState = clusterService.state(); - PersistentTasksCustomMetadata tasks = clusterState.getMetadata().custom(PersistentTasksCustomMetadata.TYPE); - PersistentTasksCustomMetadata.PersistentTask deployTrainedModelTask = - MlTasks.getTrainedModelDeploymentTask(request.getId(), tasks); - if (deployTrainedModelTask == null) { + Optional maybeAllocation = TrainedModelAllocationMetadata.allocationForModelId( + clusterService.state(), + models.get(0).getModelId() + ); + + if (maybeAllocation.isEmpty()) { listener.onResponse(new StopTrainedModelDeploymentAction.Response(true)); return; } - normalUndeploy(task, deployTrainedModelTask, request, listener); + final String modelId = models.get(0).getModelId(); + // NOTE, should only run on Master node + trainedModelAllocationClusterService.setModelAllocationToStopping( + modelId, + ActionListener.wrap( + setToStopping -> normalUndeploy(task, models.get(0).getModelId(), maybeAllocation.get(), request, listener), + failure -> { + if (ExceptionsHelper.unwrapCause(failure) instanceof ResourceNotFoundException) { + listener.onResponse(new StopTrainedModelDeploymentAction.Response(true)); + return; + } + listener.onFailure(failure); + } + ) + ); }, listener::onFailure ); @@ -117,13 +142,39 @@ private void redirectToMasterNode(DiscoveryNode masterNode, StopTrainedModelDepl } } - private void normalUndeploy(Task task, PersistentTasksCustomMetadata.PersistentTask deployTrainedModelTask, + private void normalUndeploy(Task task, + String modelId, + TrainedModelAllocation modelAllocation, StopTrainedModelDeploymentAction.Request request, ActionListener listener) { - request.setNodes(deployTrainedModelTask.getExecutorNode()); - + request.setNodes(modelAllocation.getNodeRoutingTable().keySet().toArray(String[]::new)); ActionListener finalListener = ActionListener.wrap( - r -> waitForTaskRemoved(Collections.singleton(deployTrainedModelTask.getId()), request, r, listener), + r -> { + waitForTaskRemoved(modelId, modelAllocation, request, r, ActionListener.wrap( + waited -> { + trainedModelAllocationService.deleteModelAllocation( + modelId, + ActionListener.wrap( + deleted -> listener.onResponse(r), + deletionFailed -> { + logger.error( + () -> new ParameterizedMessage( + "[{}] failed to delete model allocation after nodes unallocated the deployment", + modelId + ),deletionFailed); + listener.onFailure(ExceptionsHelper.serverError( + "failed to delete model allocation after nodes unallocated the deployment. Attempt to stop again", + deletionFailed + )); + } + ) + ); + }, + // TODO should we attempt to delete the deployment here? + listener::onFailure + )); + + }, e -> { if (ExceptionsHelper.unwrapCause(e) instanceof FailedNodeException) { // A node has dropped out of the cluster since we started executing the requests. @@ -137,22 +188,26 @@ private void normalUndeploy(Task task, PersistentTasksCustomMetadata.PersistentT } } ); - super.doExecute(task, request, finalListener); } - void waitForTaskRemoved(Set taskIds, StopTrainedModelDeploymentAction.Request request, + void waitForTaskRemoved(String modelId, + TrainedModelAllocation trainedModelAllocation, + StopTrainedModelDeploymentAction.Request request, StopTrainedModelDeploymentAction.Response response, ActionListener listener) { - persistentTasksService.waitForPersistentTasksCondition(persistentTasks -> - persistentTasks.findTasks(MlTasks.TRAINED_MODEL_DEPLOYMENT_TASK_NAME, t -> taskIds.contains(t.getId())).isEmpty(), - request.getTimeout(), ActionListener.wrap( - booleanResponse -> { - listener.onResponse(response); - }, + final Set nodesOfConcern = trainedModelAllocation.getNodeRoutingTable().keySet(); + client.admin() + .cluster() + .prepareListTasks(nodesOfConcern.toArray(String[]::new)) + .setDetailed(true) + .setWaitForCompletion(true) + .setActions(modelId) + .setTimeout(request.getTimeout()) + .execute(ActionListener.wrap( + complete -> listener.onResponse(response), listener::onFailure - ) - ); + )); } @Override @@ -172,31 +227,7 @@ protected StopTrainedModelDeploymentAction.Response newResponse(StopTrainedModel @Override protected void taskOperation(StopTrainedModelDeploymentAction.Request request, TrainedModelDeploymentTask task, ActionListener listener) { - TrainedModelDeploymentTaskState undeployingState = new TrainedModelDeploymentTaskState( - TrainedModelDeploymentState.STOPPING, task.getAllocationId(), "api"); - task.updatePersistentTaskState(undeployingState, ActionListener.wrap( - updatedTask -> { - threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(new AbstractRunnable() { - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - - @Override - protected void doRun() throws Exception { - task.stop("undeploy_trained_model (api)"); - listener.onResponse(new StopTrainedModelDeploymentAction.Response(true)); - } - }); - }, - e -> { - if (ExceptionsHelper.unwrapCause(e) instanceof ResourceNotFoundException) { - // the task has disappeared so must have stopped - listener.onResponse(new StopTrainedModelDeploymentAction.Response(true)); - } else { - listener.onFailure(e); - } - } - )); + task.stop("undeploy_trained_model (api)"); + listener.onResponse(new StopTrainedModelDeploymentAction.Response(true)); } } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateTrainedModelAllocationStateAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateTrainedModelAllocationStateAction.java new file mode 100644 index 0000000000000..3c87ab800d50f --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateTrainedModelAllocationStateAction.java @@ -0,0 +1,64 @@ +/* + * 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.ml.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction.Request; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationClusterService; + +public class TransportUpdateTrainedModelAllocationStateAction extends AcknowledgedTransportMasterNodeAction { + + private final TrainedModelAllocationClusterService trainedModelAllocationClusterService; + + @Inject + public TransportUpdateTrainedModelAllocationStateAction( + TrainedModelAllocationClusterService trainedModelAllocationClusterService, + TransportService transportService, + ClusterService clusterService, + ThreadPool threadPool, + ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver + ) { + super( + UpdateTrainedModelAllocationStateAction.NAME, + false, + transportService, + clusterService, + threadPool, + actionFilters, + Request::new, + indexNameExpressionResolver, + ThreadPool.Names.SAME + ); + this.trainedModelAllocationClusterService = trainedModelAllocationClusterService; + } + + @Override + protected void masterOperation(Task task, Request request, ClusterState state, ActionListener listener) + throws Exception { + trainedModelAllocationClusterService.updateModelRoutingTable(request, listener); + } + + @Override + protected ClusterBlockException checkBlock(Request request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterService.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterService.java new file mode 100644 index 0000000000000..8bb3e9e25b0ff --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterService.java @@ -0,0 +1,407 @@ +/* + * 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.ml.inference.allocation; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.elasticsearch.ResourceAlreadyExistsException; +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ClusterStateListener; +import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.gateway.GatewayService; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.AllocationState; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingState; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.ml.MachineLearning; +import org.elasticsearch.xpack.ml.job.NodeLoad; +import org.elasticsearch.xpack.ml.job.NodeLoadDetector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class TrainedModelAllocationClusterService implements ClusterStateListener { + + private static final Logger logger = LogManager.getLogger(TrainedModelAllocationClusterService.class); + + private final ClusterService clusterService; + private final NodeLoadDetector nodeLoadDetector; + private volatile int maxMemoryPercentage; + private volatile boolean useAuto; + + public TrainedModelAllocationClusterService(Settings settings, ClusterService clusterService, NodeLoadDetector nodeLoadDetector) { + this.clusterService = clusterService; + this.nodeLoadDetector = nodeLoadDetector; + this.maxMemoryPercentage = MachineLearning.MAX_MACHINE_MEMORY_PERCENT.get(settings); + this.useAuto = MachineLearning.USE_AUTO_MACHINE_MEMORY_PERCENT.get(settings); + // Only nodes that can possibly be master nodes really need this service running + if (DiscoveryNode.isMasterNode(settings)) { + clusterService.addListener(this); + clusterService.getClusterSettings() + .addSettingsUpdateConsumer(MachineLearning.MAX_MACHINE_MEMORY_PERCENT, this::setMaxMemoryPercentage); + clusterService.getClusterSettings() + .addSettingsUpdateConsumer(MachineLearning.USE_AUTO_MACHINE_MEMORY_PERCENT, this::setUseAuto); + } + } + + private void setMaxMemoryPercentage(int maxMemoryPercentage) { + this.maxMemoryPercentage = maxMemoryPercentage; + } + + private void setUseAuto(boolean useAuto) { + this.useAuto = useAuto; + } + + @Override + public void clusterChanged(ClusterChangedEvent event) { + if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { + return; + } + if (event.localNodeMaster() && shouldAllocateModels(event)) { + clusterService.submitStateUpdateTask("allocating models to nodes", new ClusterStateUpdateTask() { + @Override + public ClusterState execute(ClusterState currentState) { + // TODO this has a weird side-effect for allocating to nodes + // If the event indicates there were nodes added/removed, this method only looks at the current state and has + // no previous knowledge of existing nodes. Consequently, if a model was manually removed (task-kill) from a node + // it may get re-allocated to that node when another node is added/removed... + return addRemoveAllocationNodes(currentState); + } + + @Override + public void onFailure(String source, Exception e) { + logger.warn("failed to allocate models", e); + } + + @Override + public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { + logger.trace( + () -> new ParameterizedMessage( + "updated model allocations based on node changes in the cluster; new metadata [{}]", + Strings.toString(TrainedModelAllocationMetadata.fromState(newState), false, true) + ) + ); + } + }); + } + } + + public void updateModelRoutingTable( + UpdateTrainedModelAllocationStateAction.Request request, + ActionListener listener + ) { + clusterService.submitStateUpdateTask("updating model routing for node allocation", new ClusterStateUpdateTask() { + @Override + public ClusterState execute(ClusterState currentState) { + return updateModelRoutingTable(currentState, request); + } + + @Override + public void onFailure(String source, Exception e) { + listener.onFailure(e); + } + + @Override + public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { + listener.onResponse(AcknowledgedResponse.TRUE); + } + }); + } + + public void createNewModelAllocation( + StartTrainedModelDeploymentAction.TaskParams params, + ActionListener listener + ) { + clusterService.submitStateUpdateTask("create model allocation", new ClusterStateUpdateTask() { + @Override + public ClusterState execute(ClusterState currentState) { + return createModelAllocation(currentState, params); + } + + @Override + public void onFailure(String source, Exception e) { + listener.onFailure(e); + } + + @Override + public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { + listener.onResponse(TrainedModelAllocationMetadata.fromState(newState).getModelAllocation(params.getModelId())); + } + }); + } + + public void setModelAllocationToStopping(String modelId, ActionListener listener) { + clusterService.submitStateUpdateTask("set model allocation stopping", new ClusterStateUpdateTask() { + @Override + public ClusterState execute(ClusterState currentState) { + return setToStopping(currentState, modelId); + } + + @Override + public void onFailure(String source, Exception e) { + listener.onFailure(e); + } + + @Override + public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { + listener.onResponse(AcknowledgedResponse.TRUE); + } + }); + } + + public void removeModelAllocation(String modelId, ActionListener listener) { + clusterService.submitStateUpdateTask("delete model allocation", new ClusterStateUpdateTask() { + @Override + public ClusterState execute(ClusterState currentState) { + return removeAllocation(currentState, modelId); + } + + @Override + public void onFailure(String source, Exception e) { + listener.onFailure(e); + } + + @Override + public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { + listener.onResponse(AcknowledgedResponse.TRUE); + } + }); + } + + private static ClusterState update(ClusterState currentState, TrainedModelAllocationMetadata.Builder modelAllocations) { + if (modelAllocations.isChanged()) { + return ClusterState.builder(currentState) + .metadata( + Metadata.builder(currentState.metadata()).putCustom(TrainedModelAllocationMetadata.NAME, modelAllocations.build()) + ) + .build(); + } else { + return currentState; + } + } + + ClusterState createModelAllocation(ClusterState currentState, StartTrainedModelDeploymentAction.TaskParams params) { + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.builder(currentState); + if (builder.hasModel(params.getModelId())) { + throw new ResourceAlreadyExistsException("allocation for model with id [" + params.getModelId() + "] already exist"); + } + + Set shuttingDownNodes = nodesShuttingDown(currentState); + builder.addNewAllocation(params); + for (DiscoveryNode node : currentState.getNodes().getAllNodes()) { + if (StartTrainedModelDeploymentAction.TaskParams.mayAllocateToNode(node) + && shuttingDownNodes.contains(node.getId()) == false) { + Optional maybeError = nodeHasCapacity(currentState, params, node); + if (maybeError.isPresent()) { + builder.addFailedNode(params.getModelId(), node.getId(), maybeError.get()); + } else { + builder.addNode(params.getModelId(), node.getId()); + } + } + } + return update(currentState, builder); + } + + static ClusterState setToStopping(ClusterState clusterState, String modelId) { + TrainedModelAllocationMetadata metadata = TrainedModelAllocationMetadata.fromState(clusterState); + final TrainedModelAllocation existingAllocation = metadata.getModelAllocation(modelId); + if (existingAllocation == null) { + throw new ResourceNotFoundException("allocation for model with id [{}] not found", modelId); + } + // If we are stopping, don't update anything + if (existingAllocation.getAllocationState().equals(AllocationState.STOPPING)) { + return clusterState; + } + + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.builder(clusterState); + builder.setAllocationToStopping(modelId); + return update(clusterState, builder); + } + + static ClusterState updateModelRoutingTable(ClusterState currentState, UpdateTrainedModelAllocationStateAction.Request request) { + final String modelId = request.getModelId(); + final String nodeId = request.getNodeId(); + TrainedModelAllocationMetadata metadata = TrainedModelAllocationMetadata.fromState(currentState); + logger.trace( + () -> new ParameterizedMessage("[{}] [{}] current metadata before update {}", modelId, nodeId, Strings.toString(metadata)) + ); + final TrainedModelAllocation existingAllocation = metadata.getModelAllocation(modelId); + + // If state is stopped, this indicates the node process is closed, remove the node from the allocation + if (request.getRoutingState().getState().equals(RoutingState.STOPPED)) { + if (existingAllocation == null || existingAllocation.isRoutedToNode(nodeId) == false) { + return currentState; + } + return update(currentState, TrainedModelAllocationMetadata.builder(currentState).removeNode(modelId, nodeId)); + } + + if (existingAllocation == null) { + throw new ResourceNotFoundException("allocation for model with id [{}] not found", modelId); + } + // If we are stopping, don't update anything + if (existingAllocation.getAllocationState().equals(AllocationState.STOPPING)) { + logger.debug(() -> new ParameterizedMessage( + "[{}] requested update from node [{}] to update route state to [{}]", + modelId, + nodeId, + request.getRoutingState() + )); + return currentState; + } + if (existingAllocation.isRoutedToNode(nodeId) == false) { + throw new ResourceNotFoundException("allocation for model with id [{}]] is not routed to node [{}]", modelId, nodeId); + } + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.builder(currentState); + builder.updateAllocation(modelId, nodeId, request.getRoutingState()); + return update(currentState, builder); + } + + static ClusterState removeAllocation(ClusterState currentState, String modelId) { + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.builder(currentState); + if (builder.hasModel(modelId) == false) { + throw new ResourceNotFoundException("allocation for model with id [{}] not found", modelId); + } + return update(currentState, builder.removeAllocation(modelId)); + } + + ClusterState addRemoveAllocationNodes(ClusterState currentState) { + TrainedModelAllocationMetadata previousState = TrainedModelAllocationMetadata.fromState(currentState); + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.builder(currentState); + Map> removedNodeModelLookUp = new HashMap<>(); + Set shuttingDownNodes = nodesShuttingDown(currentState); + // TODO: make more efficient, right now this is O(nm) where n = sizeof(models) and m = sizeof(nodes) + // It could probably be O(max(n, m)) + // Add nodes and keep track of currently routed nodes + // Should we indicate a partial allocation somehow if some nodes don't have space? + for (Map.Entry modelAllocationEntry : previousState.modelAllocations().entrySet()) { + // Don't bother adding/removing nodes if this allocation is stopping + if (modelAllocationEntry.getValue().getAllocationState().equals(AllocationState.STOPPING)) { + continue; + } + for (DiscoveryNode node : currentState.getNodes()) { + // Only add the route if the node is NOT shutting down, this would be a weird case of the node + // just being added to the cluster and immediately shutting down... + if (shuttingDownNodes.contains(node.getId()) == false + && StartTrainedModelDeploymentAction.TaskParams.mayAllocateToNode(node) + && modelAllocationEntry.getValue().isRoutedToNode(node.getId()) == false) { + nodeHasCapacity(currentState, modelAllocationEntry.getValue().getTaskParams(), node).ifPresentOrElse( + (error) -> builder.addFailedNode(modelAllocationEntry.getKey(), node.getId(), error), + () -> builder.addNode(modelAllocationEntry.getKey(), node.getId()) + ); + } + } + for (String nodeId : modelAllocationEntry.getValue().getNodeRoutingTable().keySet()) { + removedNodeModelLookUp.computeIfAbsent(nodeId, k -> new ArrayList<>()).add(modelAllocationEntry.getKey()); + } + } + + // Remove nodes + currentState.getNodes() + .forEach( + d -> { + // If a node is referenced in the current state, we shouldn't remove the node + // But, if that node that is referenced is shutting down, we should remove the node + if (shuttingDownNodes.contains(d.getId()) == false) { + removedNodeModelLookUp.remove(d.getId()); + } + } + ); + for (Map.Entry> nodeToModels : removedNodeModelLookUp.entrySet()) { + final String nodeId = nodeToModels.getKey(); + for (String modelId : nodeToModels.getValue()) { + builder.removeNode(modelId, nodeId); + } + } + return update(currentState, builder); + } + + static boolean shouldAllocateModels(final ClusterChangedEvent event) { + // If there are no allocations created at all, there is nothing to update + final TrainedModelAllocationMetadata newMetadata = event.state().getMetadata().custom(TrainedModelAllocationMetadata.NAME); + if (newMetadata == null) { + return false; + } + if (event.nodesChanged()) { + Set shuttingDownNodes = nodesShuttingDown(event.state()); + DiscoveryNodes.Delta nodesDelta = event.nodesDelta(); + for (TrainedModelAllocation trainedModelAllocation : newMetadata.modelAllocations().values()) { + if (trainedModelAllocation.getAllocationState().equals(AllocationState.STOPPING)) { + continue; + } + for (DiscoveryNode removed : nodesDelta.removedNodes()) { + if (trainedModelAllocation.isRoutedToNode(removed.getId())) { + return true; + } + } + for (DiscoveryNode added : nodesDelta.addedNodes()) { + if (StartTrainedModelDeploymentAction.TaskParams.mayAllocateToNode(added) + && shuttingDownNodes.contains(added.getId()) == false) { + return true; + } + } + } + } + return false; + } + + Optional nodeHasCapacity(ClusterState state, StartTrainedModelDeploymentAction.TaskParams params, DiscoveryNode node) { + NodeLoad load = nodeLoadDetector.detectNodeLoad(state, true, node, Integer.MAX_VALUE, maxMemoryPercentage, useAuto); + if (Strings.isNullOrEmpty(load.getError()) == false) { + logger.warn("[{}] failed to calculate current node load with error [{}]", params.getModelId(), node.getId()); + return Optional.of(load.getError()); + } + if (load.getFreeMemory() < params.estimateMemoryUsageBytes()) { + return Optional.of( + ParameterizedMessage.format( + "This node has insufficient available memory. Available memory for ML [{} ({})], " + + "memory required by existing jobs and models [{} ({})], " + + "estimated memory required for this model [{} ({})].", + new Object[] { + load.getMaxMlMemory(), + ByteSizeValue.ofBytes(load.getMaxMlMemory()).toString(), + load.getAssignedJobMemory(), + ByteSizeValue.ofBytes(load.getAssignedJobMemory()).toString(), + params.estimateMemoryUsageBytes(), + ByteSizeValue.ofBytes(params.estimateMemoryUsageBytes()).toString() } + ) + ); + } + return Optional.empty(); + } + + /** + * Returns true if the given node is marked as shutting down with any + * shutdown type. + */ + static Set nodesShuttingDown(final ClusterState state) { + return NodesShutdownMetadata.getShutdowns(state) + .map(NodesShutdownMetadata::getAllNodeMetadataMap) + .map(Map::keySet) + .orElse(Collections.emptySet()); + } + +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadata.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadata.java new file mode 100644 index 0000000000000..fd2386c8985ac --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadata.java @@ -0,0 +1,278 @@ +/* + * 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.ml.inference.allocation; + +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.AbstractDiffable; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.Diff; +import org.elasticsearch.cluster.DiffableUtils; +import org.elasticsearch.cluster.NamedDiff; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReason; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; + +import static org.elasticsearch.cluster.metadata.Metadata.ALL_CONTEXTS; + +public class TrainedModelAllocationMetadata implements Metadata.Custom { + + private static final TrainedModelAllocationMetadata EMPTY = new TrainedModelAllocationMetadata(Collections.emptyMap()); + public static final String NAME = "trained_model_allocation"; + private final Map modelRoutingEntries; + + public static TrainedModelAllocationMetadata fromXContent(XContentParser parser) throws IOException { + return new TrainedModelAllocationMetadata(parser.map(LinkedHashMap::new, TrainedModelAllocation::fromXContent)); + } + + public static NamedDiff readDiffFrom(StreamInput in) throws IOException { + return new TrainedModelAllocationMetadata.TrainedModeAllocationDiff(in); + } + + public static Builder builder(ClusterState clusterState) { + return Builder.fromMetadata(fromState(clusterState)); + } + + public static TrainedModelAllocationMetadata fromState(ClusterState clusterState) { + TrainedModelAllocationMetadata trainedModelAllocationMetadata = clusterState.getMetadata().custom(NAME); + return trainedModelAllocationMetadata == null ? EMPTY : trainedModelAllocationMetadata; + } + + public static Optional allocationForModelId(ClusterState clusterState, String modelId) { + return Optional.ofNullable(TrainedModelAllocationMetadata.fromState(clusterState)) + .map(metadata -> metadata.getModelAllocation(modelId)); + } + + public TrainedModelAllocationMetadata(Map modelRoutingEntries) { + this.modelRoutingEntries = ExceptionsHelper.requireNonNull(modelRoutingEntries, NAME); + } + + public TrainedModelAllocationMetadata(StreamInput in) throws IOException { + this.modelRoutingEntries = in.readOrderedMap(StreamInput::readString, TrainedModelAllocation::new); + } + + public TrainedModelAllocation getModelAllocation(String modelId) { + return modelRoutingEntries.get(modelId); + } + + public Map modelAllocations() { + return Collections.unmodifiableMap(modelRoutingEntries); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.mapContents(modelRoutingEntries); + return builder; + } + + @Override + public Diff diff(Metadata.Custom previousState) { + return new TrainedModeAllocationDiff((TrainedModelAllocationMetadata) previousState, this); + } + + @Override + public EnumSet context() { + return ALL_CONTEXTS; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public Version getMinimalSupportedVersion() { + return Version.V_8_0_0; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(modelRoutingEntries, StreamOutput::writeString, (o, w) -> w.writeTo(o)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrainedModelAllocationMetadata that = (TrainedModelAllocationMetadata) o; + return Objects.equals(modelRoutingEntries, that.modelRoutingEntries); + } + + @Override + public int hashCode() { + return Objects.hash(modelRoutingEntries); + } + + public static class Builder { + + public static Builder empty(){ + return new Builder(); + } + + private final Map modelRoutingEntries; + private boolean isChanged; + + public static Builder fromMetadata(TrainedModelAllocationMetadata modelAllocationMetadata) { + return new Builder(modelAllocationMetadata); + } + + private Builder() { + modelRoutingEntries = new LinkedHashMap<>(); + } + + private Builder(TrainedModelAllocationMetadata modelAllocationMetadata) { + this.modelRoutingEntries = new LinkedHashMap<>(); + modelAllocationMetadata.modelRoutingEntries.forEach( + (modelId, allocation) -> modelRoutingEntries.put(modelId, TrainedModelAllocation.Builder.fromAllocation(allocation)) + ); + } + + public boolean hasModel(String modelId) { + return modelRoutingEntries.containsKey(modelId); + } + + Builder addNewAllocation(StartTrainedModelDeploymentAction.TaskParams taskParams) { + if (modelRoutingEntries.containsKey(taskParams.getModelId())) { + return this; + } + modelRoutingEntries.put(taskParams.getModelId(), TrainedModelAllocation.Builder.empty(taskParams)); + isChanged = true; + return this; + } + + Builder updateAllocation(String modelId, String nodeId, RoutingStateAndReason state) { + TrainedModelAllocation.Builder allocation = modelRoutingEntries.get(modelId); + if (allocation == null) { + return this; + } + isChanged |= allocation.updateExistingRoutingEntry(nodeId, state).isChanged(); + return this; + } + + Builder addNode(String modelId, String nodeId) { + TrainedModelAllocation.Builder allocation = modelRoutingEntries.get(modelId); + if (allocation == null) { + throw new ResourceNotFoundException( + "unable to add node [{}] to model [{}] routing table as allocation does not exist", + nodeId, + modelId + ); + } + isChanged |= allocation.addNewRoutingEntry(nodeId).isChanged(); + return this; + } + + Builder addFailedNode(String modelId, String nodeId, String reason) { + TrainedModelAllocation.Builder allocation = modelRoutingEntries.get(modelId); + if (allocation == null) { + throw new ResourceNotFoundException( + "unable to add failed node [{}] to model [{}] routing table as allocation does not exist", + nodeId, + modelId + ); + } + isChanged |= allocation.addNewFailedRoutingEntry(nodeId, reason).isChanged(); + return this; + } + + Builder removeNode(String modelId, String nodeId) { + TrainedModelAllocation.Builder allocation = modelRoutingEntries.get(modelId); + if (allocation == null) { + return this; + } + isChanged |= allocation.removeRoutingEntry(nodeId).isChanged(); + return this; + } + + public Builder removeAllocation(String modelId) { + isChanged |= modelRoutingEntries.remove(modelId) != null; + return this; + } + + public Builder setAllocationToStopping(String modelId) { + TrainedModelAllocation.Builder allocation = modelRoutingEntries.get(modelId); + if (allocation == null) { + throw new ResourceNotFoundException( + "unable to set model allocation [{}] to stopping as it does not exist", + modelId + ); + } + isChanged |= allocation.stopAllocation().isChanged(); + return this; + } + + public boolean isChanged() { + return isChanged; + } + + public TrainedModelAllocationMetadata build() { + Map allocations = new LinkedHashMap<>(); + modelRoutingEntries.forEach((modelId, allocation) -> allocations.put(modelId, allocation.build())); + return new TrainedModelAllocationMetadata(allocations); + } + } + + public static class TrainedModeAllocationDiff implements NamedDiff { + + private final Diff> modelRoutingEntries; + + static Diff readFrom(final StreamInput in) throws IOException { + return AbstractDiffable.readDiffFrom(TrainedModelAllocation::new, in); + } + + public TrainedModeAllocationDiff(TrainedModelAllocationMetadata before, TrainedModelAllocationMetadata after) { + this.modelRoutingEntries = DiffableUtils.diff( + before.modelRoutingEntries, + after.modelRoutingEntries, + DiffableUtils.getStringKeySerializer() + ); + } + + public TrainedModeAllocationDiff(final StreamInput in) throws IOException { + this.modelRoutingEntries = DiffableUtils.readJdkMapDiff( + in, + DiffableUtils.getStringKeySerializer(), + TrainedModelAllocation::new, + TrainedModeAllocationDiff::readFrom + ); + } + + @Override + public Metadata.Custom apply(Metadata.Custom part) { + return new TrainedModelAllocationMetadata( + new TreeMap<>(modelRoutingEntries.apply(((TrainedModelAllocationMetadata) part).modelRoutingEntries)) + ); + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + modelRoutingEntries.writeTo(out); + } + } + +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeService.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeService.java new file mode 100644 index 0000000000000..b6bc19be9e206 --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeService.java @@ -0,0 +1,380 @@ +/* + * 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.ml.inference.allocation; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.ClusterStateListener; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.component.LifecycleListener; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.tasks.TaskAwareRequest; +import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.tasks.TaskManager; +import org.elasticsearch.threadpool.Scheduler; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingState; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReason; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.core.ml.inference.results.InferenceResults; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; +import org.elasticsearch.xpack.ml.MachineLearning; +import org.elasticsearch.xpack.ml.inference.deployment.DeploymentManager; +import org.elasticsearch.xpack.ml.inference.deployment.TrainedModelDeploymentTask; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +public class TrainedModelAllocationNodeService implements ClusterStateListener { + + private static final String TASK_NAME = "trained_model_allocation"; + private static final TimeValue MODEL_LOADING_CHECK_INTERVAL = TimeValue.timeValueSeconds(1); + private static final Logger logger = LogManager.getLogger(TrainedModelAllocationNodeService.class); + private final TrainedModelAllocationService trainedModelAllocationService; + private final DeploymentManager deploymentManager; + private final TaskManager taskManager; + private final Map modelIdToTask; + private final ThreadPool threadPool; + private final Deque loadingModels; + private volatile Scheduler.Cancellable scheduledFuture; + private volatile boolean stopped; + private volatile String nodeId; + + public TrainedModelAllocationNodeService( + TrainedModelAllocationService trainedModelAllocationService, + ClusterService clusterService, + DeploymentManager deploymentManager, + TaskManager taskManager, + ThreadPool threadPool + ) { + this.trainedModelAllocationService = trainedModelAllocationService; + this.deploymentManager = deploymentManager; + this.taskManager = taskManager; + this.modelIdToTask = new ConcurrentHashMap<>(); + this.loadingModels = new ConcurrentLinkedDeque<>(); + this.threadPool = threadPool; + clusterService.addLifecycleListener(new LifecycleListener() { + @Override + public void afterStart() { + nodeId = clusterService.localNode().getId(); + start(); + } + + @Override + public void beforeStop() { + stop(); + } + }); + } + + TrainedModelAllocationNodeService( + TrainedModelAllocationService trainedModelAllocationService, + ClusterService clusterService, + DeploymentManager deploymentManager, + TaskManager taskManager, + ThreadPool threadPool, + String nodeId + ) { + this.trainedModelAllocationService = trainedModelAllocationService; + this.deploymentManager = deploymentManager; + this.taskManager = taskManager; + this.modelIdToTask = new ConcurrentHashMap<>(); + this.loadingModels = new ConcurrentLinkedDeque<>(); + this.threadPool = threadPool; + this.nodeId = nodeId; + clusterService.addLifecycleListener(new LifecycleListener() { + @Override + public void afterStart() { + start(); + } + + @Override + public void beforeStop() { + stop(); + } + }); + } + + void stopDeployment(TrainedModelDeploymentTask task) { + if (stopped) { + return; + } + deploymentManager.stopDeployment(task); + taskManager.unregister(task); + modelIdToTask.remove(task.getModelId()); + } + + void stopDeploymentAsync(TrainedModelDeploymentTask task, ActionListener listener) { + threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(() -> { + try { + stopDeployment(task); + listener.onResponse(null); + } catch (Exception e) { + listener.onFailure(e); + } + }); + } + + public void start() { + stopped = false; + scheduledFuture = threadPool.scheduleWithFixedDelay( + this::loadQueuedModels, + MODEL_LOADING_CHECK_INTERVAL, + MachineLearning.UTILITY_THREAD_POOL_NAME + ); + } + + public void stop() { + stopped = true; + ThreadPool.Cancellable cancellable = this.scheduledFuture; + if (cancellable != null) { + cancellable.cancel(); + } + } + + void loadQueuedModels() { + TrainedModelDeploymentTask loadingTask; + logger.trace("attempting to load all currently queued models"); + // NOTE: As soon as this method exits, the timer for the scheduler starts ticking + while ((loadingTask = loadingModels.poll()) != null) { + if (loadingTask.isStopped()) { + continue; + } + if (stopped) { + return; + } + final String modelId = loadingTask.getModelId(); + logger.trace(() -> new ParameterizedMessage("[{}] attempting to load model", modelId)); + final PlainActionFuture listener = new PlainActionFuture<>(); + deploymentManager.startDeployment(loadingTask, listener); + try { + // This needs to be synchronous here in the utility thread to keep queueing order + TrainedModelDeploymentTask deployedTask = listener.actionGet(); + // kicks off asynchronous cluster state update + handleLoadSuccess(deployedTask); + } catch (Exception ex) { + // kicks off asynchronous cluster state update + handleLoadFailure(loadingTask, ex); + } + } + } + + public void stopDeploymentAndNotify(TrainedModelDeploymentTask task) { + ActionListener notifyDeploymentOfStopped = ActionListener.wrap( + stopped -> updateStoredState( + task.getModelId(), + new RoutingStateAndReason(RoutingState.STOPPED, ""), + ActionListener.wrap(s -> {}, failure -> {}) + ), + failed -> { // if we failed to stop the process, something strange is going on, but we should still notify of stop + logger.warn(() -> new ParameterizedMessage("[{}] failed to stop due to error", task.getModelId()), failed); + updateStoredState( + task.getModelId(), + new RoutingStateAndReason(RoutingState.STOPPED, ""), + ActionListener.wrap(s -> {}, failure -> {}) + ); + } + ); + updateStoredState( + task.getModelId(), + new RoutingStateAndReason(RoutingState.STOPPING, "task locally canceled"), + ActionListener.wrap(success -> stopDeploymentAsync(task, notifyDeploymentOfStopped), e -> { + if (ExceptionsHelper.unwrapCause(e) instanceof ResourceNotFoundException) { + logger.debug( + () -> new ParameterizedMessage( + "[{}] failed to set routing state to stopping as allocation already removed", + task.getModelId() + ), + e + ); + } else { + // this is an unexpected error + // TODO this means requests may still be routed here, should we not stop deployment? + logger.warn( + () -> new ParameterizedMessage("[{}] failed to set routing state to stopping due to error", task.getModelId()), + e + ); + } + stopDeploymentAsync(task, notifyDeploymentOfStopped); + }) + ); + } + + public void infer(TrainedModelDeploymentTask task, String input, TimeValue timeout, ActionListener listener) { + deploymentManager.infer(task, input, timeout, listener); + } + + private TaskAwareRequest taskAwareRequest(StartTrainedModelDeploymentAction.TaskParams params) { + final TrainedModelAllocationNodeService trainedModelAllocationNodeService = this; + return new TaskAwareRequest() { + @Override + public void setParentTask(TaskId taskId) { + throw new UnsupportedOperationException("parent task id for model allocation tasks shouldn't change"); + } + + @Override + public TaskId getParentTask() { + return TaskId.EMPTY_TASK_ID; + } + + @Override + public Task createTask(long id, String type, String action, TaskId parentTaskId, Map headers) { + return new TrainedModelDeploymentTask(id, type, action, parentTaskId, headers, params, trainedModelAllocationNodeService); + } + }; + } + + @Override + public void clusterChanged(ClusterChangedEvent event) { + if (event.metadataChanged()) { + TrainedModelAllocationMetadata modelAllocationMetadata = TrainedModelAllocationMetadata.fromState(event.state()); + final String currentNode = event.state().nodes().getLocalNodeId(); + for (TrainedModelAllocation trainedModelAllocation : modelAllocationMetadata.modelAllocations().values()) { + RoutingStateAndReason routingStateAndReason = trainedModelAllocation.getNodeRoutingTable().get(currentNode); + // Add new models to start loading + if (routingStateAndReason != null + // periodic retries should be handled in a separate thread think + && routingStateAndReason.getState().equals(RoutingState.STARTING) + // This means we don't already have a task and should attempt creating one and starting the model loading + && modelIdToTask.containsKey(trainedModelAllocation.getTaskParams().getModelId()) == false) { + prepareModelToLoad(trainedModelAllocation.getTaskParams()); + } + // This mode is not routed to the current node at all + if (routingStateAndReason == null) { + TrainedModelDeploymentTask task = modelIdToTask.remove(trainedModelAllocation.getTaskParams().getModelId()); + if (task != null) { + task.stopWithoutNotification("node no longer referenced in model routing table"); + threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(() -> stopDeployment(task)); + } + } + } + List toCancel = new ArrayList<>(); + for (String modelIds : Sets.difference(modelIdToTask.keySet(), modelAllocationMetadata.modelAllocations().keySet())) { + toCancel.add(modelIdToTask.remove(modelIds)); + } + // should all be stopped in the same executor thread? + for (TrainedModelDeploymentTask t : toCancel) { + t.stopWithoutNotification("model allocation no longer exists"); + threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(() -> stopDeployment(t)); + } + } + } + + // For testing purposes + TrainedModelDeploymentTask getTask(String modelId) { + return modelIdToTask.get(modelId); + } + + void prepareModelToLoad(StartTrainedModelDeploymentAction.TaskParams taskParams) { + TrainedModelDeploymentTask task = (TrainedModelDeploymentTask) taskManager.register( + TASK_NAME, + taskParams.getModelId(), + taskAwareRequest(taskParams) + ); + // threadsafe check to verify we are not loading/loaded the model + if (modelIdToTask.putIfAbsent(taskParams.getModelId(), task) == null) { + loadingModels.add(task); + } else { + // If there is already a task for the model, unregister the new task + taskManager.unregister(task); + } + } + + private void handleLoadSuccess(TrainedModelDeploymentTask task) { + final String modelId = task.getModelId(); + logger.debug( + () -> new ParameterizedMessage("[{}] model successfully loaded and ready for inference. Notifying master node", modelId) + ); + if (task.isStopped()) { + logger.debug( + () -> new ParameterizedMessage("[{}] model loaded successfully, but stopped before routing table was updated", modelId) + ); + return; + } + updateStoredState( + modelId, + new RoutingStateAndReason(RoutingState.STARTED, ""), + ActionListener.wrap( + r -> logger.debug(() -> new ParameterizedMessage("[{}] model loaded and accepting routes", modelId)), + e -> { + // This means that either the allocation has been deleted, or this node's particular route has been removed + if (ExceptionsHelper.unwrapCause(e) instanceof ResourceNotFoundException) { + logger.debug( + () -> new ParameterizedMessage( + "[{}] model loaded but failed to start accepting routes as allocation to this node was removed", + modelId + ), + e + ); + } + // this is an unexpected error + logger.warn(() -> new ParameterizedMessage("[{}] model loaded but failed to start accepting routes", modelId), e); + } + ) + ); + } + + private void updateStoredState( + String modelId, + RoutingStateAndReason routingStateAndReason, + ActionListener listener + ) { + if (stopped) { + return; + } + trainedModelAllocationService.updateModelAllocationState( + new UpdateTrainedModelAllocationStateAction.Request(nodeId, modelId, routingStateAndReason), + ActionListener.wrap(success -> { + logger.debug( + () -> new ParameterizedMessage("[{}] model is [{}] and master notified", modelId, routingStateAndReason.getState()) + ); + listener.onResponse(AcknowledgedResponse.TRUE); + }, + error -> { + logger.warn( + () -> new ParameterizedMessage( + "[{}] model is [{}] but failed to notify master", + modelId, + routingStateAndReason.getState() + ), + error + ); + listener.onFailure(error); + } + ) + ); + } + + private void handleLoadFailure(TrainedModelDeploymentTask task, Exception ex) { + logger.error(() -> new ParameterizedMessage("[{}] model failed to load", task.getModelId()), ex); + if (task.isStopped()) { + logger.debug(() -> new ParameterizedMessage("[{}] model failed to load, but is now stopped", task.getModelId())); + } + // TODO: Do we want to remove from the modelIdToTask map? This would cause it to be reloaded by state updates on INITIALIZING + modelIdToTask.remove(task.getModelId()); + updateStoredState( + task.getModelId(), + new RoutingStateAndReason(RoutingState.FAILED, ExceptionsHelper.unwrapCause(ex).getMessage()), + ActionListener.wrap(r -> {}, e -> {}) + ); + } +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationService.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationService.java new file mode 100644 index 0000000000000..87c9415f76460 --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationService.java @@ -0,0 +1,173 @@ +/* + * 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.ml.inference.allocation; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.OriginSettingClient; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ClusterStateObserver; +import org.elasticsearch.cluster.MasterNodeChangePredicate; +import org.elasticsearch.cluster.NotMasterException; +import org.elasticsearch.cluster.coordination.FailedToCommitClusterStateException; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.node.NodeClosedException; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.ConnectTransportException; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAllocationAction; +import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAllocationAction; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; + +import java.util.Objects; +import java.util.function.Predicate; + +import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; + +public class TrainedModelAllocationService { + + private static final Logger logger = LogManager.getLogger(TrainedModelAllocationService.class); + + private final Client client; + private final ClusterService clusterService; + private final ThreadPool threadPool; + + public TrainedModelAllocationService(Client client, ClusterService clusterService, ThreadPool threadPool) { + this.client = new OriginSettingClient(client, ML_ORIGIN); + this.clusterService = Objects.requireNonNull(clusterService); + this.threadPool = Objects.requireNonNull(threadPool); + } + + public void updateModelAllocationState( + UpdateTrainedModelAllocationStateAction.Request request, + ActionListener listener + ) { + ClusterState currentState = clusterService.state(); + ClusterStateObserver observer = new ClusterStateObserver(currentState, clusterService, null, logger, threadPool.getThreadContext()); + Predicate changePredicate = MasterNodeChangePredicate.build(currentState); + DiscoveryNode masterNode = currentState.nodes().getMasterNode(); + if (masterNode == null) { + logger.warn( + "[{}] no master known for allocation state update [{}]", + request.getModelId(), + request.getRoutingState().getState() + ); + waitForNewMasterAndRetry(observer, UpdateTrainedModelAllocationStateAction.INSTANCE, request, listener, changePredicate); + return; + } + client.execute(UpdateTrainedModelAllocationStateAction.INSTANCE, request, ActionListener.wrap(listener::onResponse, failure -> { + if (isMasterChannelException(failure)) { + logger.info( + "[{}] master channel exception will retry on new master node for allocation state update [{}]", + request.getModelId(), + request.getRoutingState().getState() + ); + waitForNewMasterAndRetry(observer, UpdateTrainedModelAllocationStateAction.INSTANCE, request, listener, changePredicate); + return; + } + listener.onFailure(failure); + })); + } + + public void createNewModelAllocation( + StartTrainedModelDeploymentAction.TaskParams taskParams, + ActionListener listener + ) { + client.execute(CreateTrainedModelAllocationAction.INSTANCE, new CreateTrainedModelAllocationAction.Request(taskParams), listener); + } + + public void deleteModelAllocation(String modelId, ActionListener listener) { + client.execute(DeleteTrainedModelAllocationAction.INSTANCE, new DeleteTrainedModelAllocationAction.Request(modelId), listener); + } + + public void waitForAllocationCondition( + final String modelId, + final Predicate predicate, + final @Nullable TimeValue timeout, + final WaitForAllocationListener listener + ) { + final Predicate clusterStatePredicate = clusterState -> predicate.test( + TrainedModelAllocationMetadata.allocationForModelId(clusterState, modelId).orElse(null) + ); + + final ClusterStateObserver observer = new ClusterStateObserver(clusterService, timeout, logger, threadPool.getThreadContext()); + final ClusterState clusterState = observer.setAndGetObservedState(); + if (clusterStatePredicate.test(clusterState)) { + listener.onResponse(TrainedModelAllocationMetadata.allocationForModelId(clusterState, modelId).orElse(null)); + } else { + observer.waitForNextChange(new ClusterStateObserver.Listener() { + @Override + public void onNewClusterState(ClusterState state) { + listener.onResponse(TrainedModelAllocationMetadata.allocationForModelId(state, modelId).orElse(null)); + } + + @Override + public void onClusterServiceClose() { + listener.onFailure(new NodeClosedException(clusterService.localNode())); + } + + @Override + public void onTimeout(TimeValue timeout) { + listener.onTimeout(timeout); + } + }, clusterStatePredicate); + } + } + + public interface WaitForAllocationListener extends ActionListener { + default void onTimeout(TimeValue timeout) { + onFailure(new IllegalStateException("Timed out when waiting for trained model allocation after " + timeout)); + } + } + + protected void waitForNewMasterAndRetry( + ClusterStateObserver observer, + ActionType action, + ActionRequest request, + ActionListener listener, + Predicate changePredicate + ) { + observer.waitForNextChange(new ClusterStateObserver.Listener() { + @Override + public void onNewClusterState(ClusterState state) { + client.execute(action, request, listener); + } + + @Override + public void onClusterServiceClose() { + logger.warn("node closed while execution action [{}] for request [{}]", action.name(), request); + listener.onFailure(new NodeClosedException(clusterService.localNode())); + } + + @Override + public void onTimeout(TimeValue timeout) { + // we wait indefinitely for a new master + assert false; + } + }, changePredicate); + } + + private static final Class[] MASTER_CHANNEL_EXCEPTIONS = new Class[] { + NotMasterException.class, + ConnectTransportException.class, + FailedToCommitClusterStateException.class }; + + private static boolean isMasterChannelException(Exception exp) { + return org.elasticsearch.ExceptionsHelper.unwrap(exp, MASTER_CHANNEL_EXCEPTIONS) != null; + } + +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java index 15d236831b54a..a57a4f374b012 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchRequest; @@ -26,12 +27,9 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.core.TimeValue; import org.elasticsearch.index.query.IdsQueryBuilder; -import org.elasticsearch.persistent.PersistentTasksCustomMetadata; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHit; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.xpack.core.ml.inference.deployment.TrainedModelDeploymentState; -import org.elasticsearch.xpack.core.ml.inference.deployment.TrainedModelDeploymentTaskState; import org.elasticsearch.xpack.core.ml.inference.results.InferenceResults; import org.elasticsearch.xpack.core.ml.job.messages.Messages; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; @@ -45,7 +43,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.Locale; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -77,17 +74,18 @@ public DeploymentManager(Client client, NamedXContentRegistry xContentRegistry, this.executorServiceForProcess = threadPool.executor(MachineLearning.JOB_COMMS_THREAD_POOL_NAME); } - public void startDeployment(TrainedModelDeploymentTask task) { - doStartDeployment(task); + public void startDeployment(TrainedModelDeploymentTask task, ActionListener listener) { + doStartDeployment(task, listener); } - private void doStartDeployment(TrainedModelDeploymentTask task) { + private void doStartDeployment(TrainedModelDeploymentTask task, ActionListener listener) { logger.debug("[{}] Starting model deployment", task.getModelId()); ProcessContext processContext = new ProcessContext(task.getModelId(), task.getIndex(), executorServiceForProcess); - if (processContextByAllocation.putIfAbsent(task.getAllocationId(), processContext) != null) { - throw ExceptionsHelper.serverError("[{}] Could not create process as one already exists", task.getModelId()); + if (processContextByAllocation.putIfAbsent(task.getId(), processContext) != null) { + listener.onFailure(ExceptionsHelper.serverError("[{}] Could not create process as one already exists", task.getModelId())); + return; } String taskConfigDocId = NlpTaskConfig.documentId(task.getModelId()); @@ -95,22 +93,19 @@ private void doStartDeployment(TrainedModelDeploymentTask task) { ActionListener modelLoadedListener = ActionListener.wrap( success -> { executorServiceForProcess.execute(() -> processContext.resultProcessor.process(processContext.process.get())); - - setTaskStateToStarted(task, ActionListener.wrap( - response -> logger.info("[{}] trained model loaded", task.getModelId()), - e -> failTask(task, - String.format(Locale.ROOT, "[%s] error setting task state to [%s] [%s]", - task.getModelId(), TrainedModelDeploymentState.STARTED, e)) - )); + listener.onResponse(task); }, - e -> failTask(task, - String.format(Locale.ROOT, "[%s] error loading model [%s]", task.getModelId(), e)) + listener::onFailure ); ActionListener configListener = ActionListener.wrap( searchResponse -> { if (searchResponse.getHits().getHits().length == 0) { - failTask(task, Messages.getMessage(Messages.TASK_CONFIG_NOT_FOUND, task.getModelId(), taskConfigDocId)); + listener.onFailure( + new ResourceNotFoundException( + Messages.getMessage(Messages.TASK_CONFIG_NOT_FOUND, task.getModelId(), taskConfigDocId) + ) + ); return; } @@ -121,10 +116,9 @@ private void doStartDeployment(TrainedModelDeploymentTask task) { // here, we are being called back on the searching thread, which MAY be a network thread // `startAndLoad` creates named pipes, blocking the calling thread, better to execute that in our utility // executor. - executorServiceForDeployment.execute(() -> startAndLoad(task, processContext, modelLoadedListener)); + executorServiceForProcess.execute(() -> startAndLoad(processContext, modelLoadedListener)); }, - e -> failTask(task, - String.format(Locale.ROOT, "[%s] creating NLP task from configuration failed with error [%s]", task.getModelId(), e)) + listener::onFailure ); SearchRequest searchRequest = taskConfigSearchRequest(taskConfigDocId, task.getIndex()); @@ -151,22 +145,19 @@ NlpTaskConfig parseConfigDocLeniently(SearchHit hit) throws IOException { } } - private void startAndLoad(TrainedModelDeploymentTask task, - ProcessContext processContext, - ActionListener loadedListener) { + private void startAndLoad(ProcessContext processContext, ActionListener loadedListener) { try { processContext.startProcess(); processContext.loadModel(loadedListener); } catch (Exception e) { - failTask(task, - String.format(Locale.ROOT, "[%s] loading the model failed with error [%s]", task.getModelId(), e)); + loadedListener.onFailure(e); } } public void stopDeployment(TrainedModelDeploymentTask task) { ProcessContext processContext; synchronized (processContextByAllocation) { - processContext = processContextByAllocation.get(task.getAllocationId()); + processContext = processContextByAllocation.get(task.getId()); } if (processContext != null) { logger.info("[{}] Stopping deployment", task.getModelId()); @@ -179,7 +170,7 @@ public void stopDeployment(TrainedModelDeploymentTask task) { public void infer(TrainedModelDeploymentTask task, String input, TimeValue timeout, ActionListener listener) { - ProcessContext processContext = processContextByAllocation.get(task.getAllocationId()); + ProcessContext processContext = processContextByAllocation.get(task.getId()); if (processContext == null) { listener.onFailure(new IllegalStateException("[" + task.getModelId() + "] process context missing")); @@ -248,27 +239,6 @@ private void waitForResult(ProcessContext processContext, } } - private void setTaskStateToStarted(TrainedModelDeploymentTask task, - ActionListener> listener) { - TrainedModelDeploymentTaskState startedState = new TrainedModelDeploymentTaskState( - TrainedModelDeploymentState.STARTED, task.getAllocationId(), null); - task.updatePersistentTaskState(startedState, listener); - } - private void failTask(TrainedModelDeploymentTask task, - String reason) { - - logger.error("[{}] failed with reason [{}]", task.getModelId(), reason); - - TrainedModelDeploymentTaskState taskState = - new TrainedModelDeploymentTaskState(TrainedModelDeploymentState.FAILED, task.getAllocationId(), reason); - - task.updatePersistentTaskState(taskState, ActionListener.wrap( - persistentTask -> {}, - e -> logger.error(new ParameterizedMessage("[{}] error setting model deployment state to failed. " + - "Failure reason: [{}]", task.getModelId(), reason), e) - )); - } - class ProcessContext { private final String modelId; diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/TrainedModelDeploymentTask.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/TrainedModelDeploymentTask.java index 7bfd604863b6c..2a6d6ffe08e8c 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/TrainedModelDeploymentTask.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/TrainedModelDeploymentTask.java @@ -11,26 +11,40 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.core.TimeValue; -import org.elasticsearch.persistent.AllocatedPersistentTask; +import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction.TaskParams; import org.elasticsearch.xpack.core.ml.inference.results.InferenceResults; +import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; +import org.elasticsearch.xpack.ml.inference.allocation.TrainedModelAllocationNodeService; import java.util.Map; -public class TrainedModelDeploymentTask extends AllocatedPersistentTask implements StartTrainedModelDeploymentAction.TaskMatcher { +public class TrainedModelDeploymentTask extends CancellableTask implements StartTrainedModelDeploymentAction.TaskMatcher { private static final Logger logger = LogManager.getLogger(TrainedModelDeploymentTask.class); private final TaskParams params; - private volatile DeploymentManager manager; + private final TrainedModelAllocationNodeService trainedModelAllocationNodeService; + private volatile boolean stopped; - public TrainedModelDeploymentTask(long id, String type, String action, TaskId parentTask, Map headers, - TaskParams taskParams) { + public TrainedModelDeploymentTask( + long id, + String type, + String action, + TaskId parentTask, + Map headers, + TaskParams taskParams, + TrainedModelAllocationNodeService trainedModelAllocationNodeService + ) { super(id, type, action, MlTasks.TRAINED_MODEL_DEPLOYMENT_TASK_ID_PREFIX + taskParams.getModelId(), parentTask, headers); this.params = taskParams; + this.trainedModelAllocationNodeService = ExceptionsHelper.requireNonNull( + trainedModelAllocationNodeService, + "trainedModelAllocationNodeService" + ); } public String getModelId() { @@ -47,14 +61,17 @@ public long estimateMemoryUsageBytes() { public void stop(String reason) { logger.debug("[{}] Stopping due to reason [{}]", getModelId(), reason); + stopped = true; + trainedModelAllocationNodeService.stopDeploymentAndNotify(this); + } - assert manager != null : "manager should not be unset when stop is called"; - manager.stopDeployment(this); - markAsCompleted(); + public void stopWithoutNotification(String reason) { + logger.debug("[{}] Stopping due to reason [{}]", getModelId(), reason); + stopped = true; } - public void setDeploymentManager(DeploymentManager manager) { - this.manager = manager; + public boolean isStopped() { + return stopped; } @Override @@ -64,6 +81,6 @@ protected void onCancelled() { } public void infer(String input, TimeValue timeout, ActionListener listener) { - manager.infer(this, input, timeout, listener); + trainedModelAllocationNodeService.infer(this, input, timeout, listener); } } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java index 8ee5ad193a13a..8342aa1d77e95 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java @@ -326,7 +326,7 @@ public static String nodeNameAndVersion(DiscoveryNode node) { return builder.toString(); } - static String nodeNameAndMlAttributes(DiscoveryNode node) { + public static String nodeNameAndMlAttributes(DiscoveryNode node) { String nodeNameOrID = nodeNameOrId(node); StringBuilder builder = new StringBuilder("{").append(nodeNameOrID).append('}'); diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterServiceTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterServiceTests.java new file mode 100644 index 0000000000000..31fcc1bbe50f6 --- /dev/null +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationClusterServiceTests.java @@ -0,0 +1,670 @@ +/* + * 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.ml.inference.allocation; + +import org.elasticsearch.ResourceAlreadyExistsException; +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; +import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodeRole; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.AllocationState; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingState; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReason; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.ml.MachineLearning; +import org.elasticsearch.xpack.ml.job.NodeLoadDetector; +import org.elasticsearch.xpack.ml.process.MlMemoryTracker; +import org.junit.Before; + +import java.util.Collections; +import java.util.Set; +import java.util.function.Function; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TrainedModelAllocationClusterServiceTests extends ESTestCase { + + private ClusterService clusterService; + private NodeLoadDetector nodeLoadDetector; + + @Before + public void setupObjects() { + clusterService = mock(ClusterService.class); + ClusterSettings clusterSettings = new ClusterSettings( + Settings.EMPTY, + Sets.newHashSet(MachineLearning.MAX_MACHINE_MEMORY_PERCENT, MachineLearning.USE_AUTO_MACHINE_MEMORY_PERCENT) + ); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + MlMemoryTracker memoryTracker = mock(MlMemoryTracker.class); + when(memoryTracker.isRecentlyRefreshed()).thenReturn(true); + nodeLoadDetector = new NodeLoadDetector(memoryTracker); + } + + public void testUpdateModelRoutingTable() { + String modelId = "existing-model"; + String nodeId = "ml-node-with-room"; + ClusterState currentState = ClusterState.builder(new ClusterName("testUpdateModelRoutingTable")) + .nodes(DiscoveryNodes.builder().add(buildNode("ml-node-with-room", true, ByteSizeValue.ofGb(4).getBytes())).build()) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(modelId, 10_000L)) + .addNode(modelId, nodeId) + .build() + ) + .build() + ) + .build(); + + assertThatStoppingAllocationPreventsMutation( + state -> TrainedModelAllocationClusterService.updateModelRoutingTable( + state, + new UpdateTrainedModelAllocationStateAction.Request(nodeId, modelId, new RoutingStateAndReason(RoutingState.STARTED, "")) + ), + currentState + ); + + ClusterState newState = TrainedModelAllocationClusterService.updateModelRoutingTable( + currentState, + new UpdateTrainedModelAllocationStateAction.Request(nodeId, modelId, new RoutingStateAndReason(RoutingState.STARTED, "")) + ); + assertThat( + TrainedModelAllocationMetadata.fromState(newState).getModelAllocation(modelId).getNodeRoutingTable().get(nodeId).getState(), + equalTo(RoutingState.STARTED) + ); + + expectThrows( + ResourceNotFoundException.class, + () -> TrainedModelAllocationClusterService.updateModelRoutingTable( + currentState, + new UpdateTrainedModelAllocationStateAction.Request( + "missingNode", + modelId, + new RoutingStateAndReason(RoutingState.STARTED, "") + ) + ) + ); + expectThrows( + ResourceNotFoundException.class, + () -> TrainedModelAllocationClusterService.updateModelRoutingTable( + currentState, + new UpdateTrainedModelAllocationStateAction.Request( + nodeId, + "missingModel", + new RoutingStateAndReason(RoutingState.STARTED, "") + ) + ) + ); + + // TEST Stopped + + // We should allow a "stopped" update on missing models and nodes as entries may have already been deleted + TrainedModelAllocationClusterService.updateModelRoutingTable( + currentState, + new UpdateTrainedModelAllocationStateAction.Request("missingNode", modelId, new RoutingStateAndReason(RoutingState.STOPPED, "")) + ); + TrainedModelAllocationClusterService.updateModelRoutingTable( + currentState, + new UpdateTrainedModelAllocationStateAction.Request(nodeId, "missingModel", new RoutingStateAndReason(RoutingState.STOPPED, "")) + ); + + ClusterState updateState = TrainedModelAllocationClusterService.updateModelRoutingTable( + currentState, + new UpdateTrainedModelAllocationStateAction.Request(nodeId, modelId, new RoutingStateAndReason(RoutingState.STOPPED, "")) + ); + assertThat( + TrainedModelAllocationMetadata.fromState(updateState).getModelAllocation(modelId).getNodeRoutingTable(), + not(hasKey(nodeId)) + ); + } + + public void testRemoveAllocation() { + ClusterState clusterStateWithoutAllocation = ClusterState.builder(new ClusterName("testRemoveAllocation")) + .metadata(Metadata.builder().build()) + .build(); + String modelId = "remove-allocation"; + + expectThrows( + ResourceNotFoundException.class, + () -> TrainedModelAllocationClusterService.removeAllocation(clusterStateWithoutAllocation, modelId) + ); + + ClusterState clusterStateWithAllocation = ClusterState.builder(new ClusterName("testRemoveAllocation")) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(modelId, randomNonNegativeLong())).build() + ) + .build() + ) + .build(); + assertThat(TrainedModelAllocationMetadata.fromState(clusterStateWithAllocation).getModelAllocation(modelId), is(not(nullValue()))); + + ClusterState modified = TrainedModelAllocationClusterService.removeAllocation(clusterStateWithAllocation, modelId); + assertThat(TrainedModelAllocationMetadata.fromState(modified).getModelAllocation(modelId), is(nullValue())); + + } + + public void testCreateAllocation() { + ClusterState currentState = ClusterState.builder(new ClusterName("testCreateAllocation")) + .nodes( + DiscoveryNodes.builder() + .add(buildNode("ml-node-with-room", true, ByteSizeValue.ofGb(4).getBytes())) + .add(buildNode("ml-node-without-room", true, 1000L)) + .add(buildNode("not-ml-node", false, ByteSizeValue.ofGb(4).getBytes())) + .add(buildNode("ml-node-shutting-down", true, ByteSizeValue.ofGb(4).getBytes())) + .build() + ) + .metadata(Metadata.builder().putCustom(NodesShutdownMetadata.TYPE, shutdownMetadata("ml-node-shutting-down"))) + .build(); + + TrainedModelAllocationClusterService trainedModelAllocationClusterService = createClusterService(); + ClusterState newState = trainedModelAllocationClusterService.createModelAllocation(currentState, newParams("new-model", 150)); + TrainedModelAllocation createdAllocation = TrainedModelAllocationMetadata.fromState(newState).getModelAllocation("new-model"); + + assertThat(createdAllocation, is(not(nullValue()))); + assertThat(createdAllocation.getNodeRoutingTable().keySet(), hasSize(2)); + assertThat(createdAllocation.getNodeRoutingTable(), hasKey("ml-node-with-room")); + assertThat(createdAllocation.getNodeRoutingTable().get("ml-node-with-room").getState(), equalTo(RoutingState.STARTING)); + assertThat(createdAllocation.getNodeRoutingTable(), hasKey("ml-node-without-room")); + assertThat(createdAllocation.getNodeRoutingTable().get("ml-node-without-room").getState(), equalTo(RoutingState.FAILED)); + assertThat( + createdAllocation.getNodeRoutingTable().get("ml-node-without-room").getReason(), + containsString("This node has insufficient available memory.") + ); + + expectThrows( + ResourceAlreadyExistsException.class, + () -> trainedModelAllocationClusterService.createModelAllocation(newState, newParams("new-model", 150)) + ); + } + + public void testAddRemoveAllocationNodes() { + ClusterState currentState = ClusterState.builder(new ClusterName("testAddRemoveAllocationNodes")) + .nodes( + DiscoveryNodes.builder() + .add(buildNode("ml-node-with-room", true, ByteSizeValue.ofGb(4).getBytes())) + .add(buildNode("new-ml-node-with-room", true, ByteSizeValue.ofGb(4).getBytes())) + .add(buildNode("ml-node-without-room", true, 1000L)) + .add(buildNode("not-ml-node", false, ByteSizeValue.ofGb(4).getBytes())) + .add(buildNode("ml-node-shutting-down", true, ByteSizeValue.ofGb(4).getBytes())) + .build() + ) + .metadata( + Metadata.builder() + .putCustom(NodesShutdownMetadata.TYPE, shutdownMetadata("ml-node-shutting-down")) + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams("model-1", 10_000)) + .addNode("model-1", "ml-node-with-room") + .updateAllocation("model-1", "ml-node-with-room", new RoutingStateAndReason(RoutingState.STARTED, "")) + .addNode("model-1", "old-ml-node-with-room") + .updateAllocation("model-1", "old-ml-node-with-room", new RoutingStateAndReason(RoutingState.STARTED, "")) + .addNode("model-1", "ml-node-shutting-down") + .addNewAllocation(newParams("model-2", 10_000)) + .addNode("model-2", "old-ml-node-with-room") + .updateAllocation("model-2", "old-ml-node-with-room", new RoutingStateAndReason(RoutingState.STARTED, "")) + .build() + ) + ) + .build(); + TrainedModelAllocationClusterService trainedModelAllocationClusterService = createClusterService(); + + // Stopping shouldn't cause any updates + assertThatStoppingAllocationPreventsMutation( + trainedModelAllocationClusterService::addRemoveAllocationNodes, + currentState + ); + + ClusterState modified = trainedModelAllocationClusterService.addRemoveAllocationNodes(currentState); + TrainedModelAllocationMetadata trainedModelAllocationMetadata = TrainedModelAllocationMetadata.fromState(modified); + assertThat(trainedModelAllocationMetadata.modelAllocations().keySet(), hasSize(2)); + assertThat(trainedModelAllocationMetadata.modelAllocations(), allOf(hasKey("model-1"), hasKey("model-2"))); + + assertThat(trainedModelAllocationMetadata.getModelAllocation("model-1").getNodeRoutingTable().keySet(), hasSize(3)); + assertThat( + trainedModelAllocationMetadata.getModelAllocation("model-1").getNodeRoutingTable(), + allOf(hasKey("ml-node-with-room"), hasKey("new-ml-node-with-room"), hasKey("ml-node-without-room")) + ); + assertNodeState(trainedModelAllocationMetadata, "model-1", "ml-node-with-room", RoutingState.STARTED); + assertNodeState(trainedModelAllocationMetadata, "model-1", "new-ml-node-with-room", RoutingState.STARTING); + assertNodeState(trainedModelAllocationMetadata, "model-1", "ml-node-without-room", RoutingState.FAILED); + + assertThat(trainedModelAllocationMetadata.getModelAllocation("model-2").getNodeRoutingTable().keySet(), hasSize(3)); + assertThat( + trainedModelAllocationMetadata.getModelAllocation("model-2").getNodeRoutingTable(), + allOf(hasKey("ml-node-with-room"), hasKey("new-ml-node-with-room"), hasKey("ml-node-without-room")) + ); + assertNodeState(trainedModelAllocationMetadata, "model-2", "ml-node-with-room", RoutingState.STARTING); + assertNodeState(trainedModelAllocationMetadata, "model-2", "new-ml-node-with-room", RoutingState.STARTING); + assertNodeState(trainedModelAllocationMetadata, "model-2", "ml-node-without-room", RoutingState.FAILED); + } + + public void testShouldAllocateModels() { + String mlNode1 = "ml-node-with-room"; + String mlNode2 = "new-ml-node-with-room"; + DiscoveryNode mlNode1Node = buildNode(mlNode1, true, ByteSizeValue.ofGb(4).getBytes()); + DiscoveryNode mlNode2Node = buildNode(mlNode2, true, ByteSizeValue.ofGb(4).getBytes()); + ClusterState stateWithTwoNodes = ClusterState.builder(new ClusterName("testShouldAllocateModels")) + .nodes(DiscoveryNodes.builder().add(mlNode1Node).add(mlNode2Node)) + .build(); + ClusterState stateWithOneNode = ClusterState.builder(new ClusterName("testShouldAllocateModels")) + .nodes(DiscoveryNodes.builder().add(mlNode1Node)) + .build(); + ClusterState stateWithOneNodeNotMl = ClusterState.builder(new ClusterName("testShouldAllocateModels")) + .nodes(DiscoveryNodes.builder().add(mlNode1Node).add(buildNode("not-ml-node", false, ByteSizeValue.ofGb(4).getBytes()))) + .build(); + + // No metadata in the new state means no allocations, so no updates + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(randomFrom(stateWithOneNodeNotMl, stateWithOneNode, stateWithTwoNodes)).build(), + ClusterState.builder(randomFrom(stateWithOneNodeNotMl, stateWithOneNode, stateWithTwoNodes)) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build() + ) + ), + is(false) + ); + + // Even with metadata changes, unless there are node changes, do nothing + ClusterState randomState = randomFrom(stateWithOneNodeNotMl, stateWithOneNode, stateWithTwoNodes); + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(randomState) + .metadata( + Metadata.builder() + .putCustom(TrainedModelAllocationMetadata.NAME, TrainedModelAllocationMetadataTests.randomInstance()) + .build() + ) + .build(), + ClusterState.builder(randomState) + .metadata( + Metadata.builder() + .putCustom(TrainedModelAllocationMetadata.NAME, TrainedModelAllocationMetadataTests.randomInstance()) + .build() + ) + .build() + ) + ), + is(false) + ); + + // If the node removed is not even an ML node, we should not attempt to re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build(), + ClusterState.builder(stateWithOneNodeNotMl) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build() + ) + ), + is(false) + ); + + // If the node removed is an ML node, but no models are allocated to it, we should not attempt to re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build(), + ClusterState.builder(stateWithTwoNodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build() + ) + ), + is(false) + ); + + // If a new ML node is added, we should attempt to re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithTwoNodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build(), + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build() + ) + ), + is(true) + ); + + // If a new ML node is added, but allocation is stopping, we should not re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithTwoNodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty() + .addNewAllocation(newParams(mlNode1, 100)) + .setAllocationToStopping(mlNode1) + .build() + ) + .build() + ) + .build(), + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build() + ) + ), + is(false) + ); + + // If a new ML node is added, but its shutting down, don't re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithTwoNodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .putCustom(NodesShutdownMetadata.TYPE, shutdownMetadata(mlNode2)) + .build() + ) + .build(), + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(mlNode1, 100)).build() + ) + .build() + ) + .build() + ) + ), + is(false) + ); + + // If a ML node is removed and its routed to, re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams("model-1", 100)) + .addNode("model-1", mlNode1) + .addNewAllocation(newParams("model-2", 100)) + .addNode("model-2", mlNode1) + .addNode("model-2", mlNode2) + .build() + ) + .build() + ) + .build(), + ClusterState.builder(stateWithTwoNodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams("model-1", 100)) + .addNode("model-1", mlNode1) + .addNewAllocation(newParams("model-2", 100)) + .addNode("model-2", mlNode1) + .addNode("model-2", mlNode2) + .build() + ) + .build() + ) + .build() + ) + ), + is(true) + ); + + // If a ML node is removed and its routed to, but the allocation is stopping, don't re-allocate + assertThat( + TrainedModelAllocationClusterService.shouldAllocateModels( + new ClusterChangedEvent( + "test", + ClusterState.builder(stateWithOneNode) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty() + .addNewAllocation(newParams("model-1", 100)) + .addNode("model-1", mlNode1) + .addNewAllocation(newParams("model-2", 100)) + .addNode("model-2", mlNode1) + .addNode("model-2", mlNode2) + .setAllocationToStopping("model-2") + .build() + ) + .build() + ) + .build(), + ClusterState.builder(stateWithTwoNodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty() + .addNewAllocation(newParams("model-1", 100)) + .addNode("model-1", mlNode1) + .addNewAllocation(newParams("model-2", 100)) + .addNode("model-2", mlNode1) + .addNode("model-2", mlNode2) + .build() + ) + .build() + ) + .build() + ) + ), + is(false) + ); + } + + public void testSetAllocationToStopping() { + ClusterState clusterStateWithoutAllocation = ClusterState.builder(new ClusterName("testSetAllocationToStopping")) + .metadata(Metadata.builder().build()) + .build(); + String modelId = "stopping-allocation"; + + expectThrows( + ResourceNotFoundException.class, + () -> TrainedModelAllocationClusterService.setToStopping(clusterStateWithoutAllocation, modelId) + ); + + ClusterState clusterStateWithAllocation = ClusterState.builder(new ClusterName("testSetAllocationToStopping")) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty().addNewAllocation(newParams(modelId, randomNonNegativeLong())).build() + ) + .build() + ) + .build(); + TrainedModelAllocationMetadata before = TrainedModelAllocationMetadata.fromState(clusterStateWithAllocation); + assertThat(before.getModelAllocation(modelId), is(not(nullValue()))); + assertThat(before.getModelAllocation(modelId).getAllocationState(), equalTo(AllocationState.STARTED)); + + ClusterState modified = TrainedModelAllocationClusterService.setToStopping(clusterStateWithAllocation, modelId); + assertThat( + TrainedModelAllocationMetadata.fromState(modified).getModelAllocation(modelId).getAllocationState(), + equalTo(AllocationState.STOPPING) + ); + } + + private void assertThatStoppingAllocationPreventsMutation( + Function mutationFunction, + ClusterState original + ) { + TrainedModelAllocationMetadata tempMetadata = TrainedModelAllocationMetadata.fromState(original); + if (tempMetadata.modelAllocations().isEmpty()) { + return; + } + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.builder(original); + for (String modelId : tempMetadata.modelAllocations().keySet()) { + builder.setAllocationToStopping(modelId); + } + TrainedModelAllocationMetadata metadataWithStopping = builder.build(); + ClusterState originalWithStoppingAllocations = ClusterState.builder(original) + .metadata(Metadata.builder(original.metadata()).putCustom(TrainedModelAllocationMetadata.NAME, metadataWithStopping).build()) + .build(); + + assertThat( + "setting all allocations to stopping did not prevent mutation", + TrainedModelAllocationMetadata.fromState(mutationFunction.apply(originalWithStoppingAllocations)), + equalTo(metadataWithStopping) + ); + } + + private TrainedModelAllocationClusterService createClusterService() { + return new TrainedModelAllocationClusterService(Settings.EMPTY, clusterService, nodeLoadDetector); + } + + private static DiscoveryNode buildNode(String name, boolean isML, long nativeMemory) { + return new DiscoveryNode( + name, + name, + buildNewFakeTransportAddress(), + MapBuilder.newMapBuilder() + .put(MachineLearning.MACHINE_MEMORY_NODE_ATTR, String.valueOf(nativeMemory)) + .put(MachineLearning.MAX_JVM_SIZE_NODE_ATTR, String.valueOf(10)) + .put(MachineLearning.MAX_OPEN_JOBS_NODE_ATTR, String.valueOf(10)) + .map(), + isML ? DiscoveryNodeRole.roles() : Set.of(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE), + Version.CURRENT + ); + } + + private static StartTrainedModelDeploymentAction.TaskParams newParams(String modelId, long modelSize) { + return new StartTrainedModelDeploymentAction.TaskParams(modelId, "test-index", modelSize); + } + + private static void assertNodeState(TrainedModelAllocationMetadata metadata, String modelId, String nodeId, RoutingState routingState) { + assertThat(metadata.getModelAllocation(modelId).getNodeRoutingTable().get(nodeId).getState(), equalTo(routingState)); + } + + private static NodesShutdownMetadata shutdownMetadata(String nodeId) { + return new NodesShutdownMetadata( + Collections.singletonMap( + nodeId, + SingleNodeShutdownMetadata.builder() + .setType(SingleNodeShutdownMetadata.Type.REMOVE) + .setStartedAtMillis(randomNonNegativeLong()) + .setReason("tests") + .setNodeId(nodeId) + .build() + ) + ); + } +} diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadataTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadataTests.java new file mode 100644 index 0000000000000..8bdd7390e1aa4 --- /dev/null +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationMetadataTests.java @@ -0,0 +1,108 @@ +/* + * 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.ml.inference.allocation; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingStateAndReasonTests; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocation; +import org.elasticsearch.xpack.core.ml.inference.allocation.TrainedModelAllocationTests; + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.is; + +public class TrainedModelAllocationMetadataTests extends AbstractSerializingTestCase { + + public static TrainedModelAllocationMetadata randomInstance() { + LinkedHashMap map = Stream.generate(() -> randomAlphaOfLength(10)) + .limit(randomInt(5)) + .collect( + Collectors.toMap(Function.identity(), (k) -> TrainedModelAllocationTests.randomInstance(), (k, k1) -> k, LinkedHashMap::new) + ); + return new TrainedModelAllocationMetadata(map); + } + + @Override + protected TrainedModelAllocationMetadata doParseInstance(XContentParser parser) throws IOException { + return TrainedModelAllocationMetadata.fromXContent(parser); + } + + @Override + protected Writeable.Reader instanceReader() { + return TrainedModelAllocationMetadata::new; + } + + @Override + protected TrainedModelAllocationMetadata createTestInstance() { + return new TrainedModelAllocationMetadata(new HashMap<>()); + } + + public void testBuilderChanged_WhenAddingRemovingModel() { + TrainedModelAllocationMetadata original = randomInstance(); + String newModel = "foo_model"; + + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.Builder.fromMetadata(original); + assertThat(builder.isChanged(), is(false)); + + assertUnchanged(builder, b -> b.removeAllocation(newModel)); + assertUnchanged(builder, b -> b.updateAllocation(newModel, "foo", RoutingStateAndReasonTests.randomInstance())); + assertUnchanged(builder, b -> b.removeNode(newModel, "foo")); + + if (original.modelAllocations().isEmpty() == false) { + String randomExistingModel = randomFrom(original.modelAllocations().keySet().toArray(String[]::new)); + assertUnchanged(builder, b -> b.addNewAllocation(randomParams(randomExistingModel))); + } + + builder.addNewAllocation(new StartTrainedModelDeploymentAction.TaskParams(newModel, "test-index", randomNonNegativeLong())); + assertThat(builder.isChanged(), is(true)); + } + + public void testBuilderChanged_WhenAddingRemovingNodeFromModel() { + String newModel = "foo_model"; + TrainedModelAllocationMetadata original = TrainedModelAllocationMetadata.Builder.fromMetadata(randomInstance()) + .addNewAllocation(randomParams(newModel)) + .build(); + TrainedModelAllocationMetadata.Builder builder = TrainedModelAllocationMetadata.Builder.fromMetadata(original); + assertThat(builder.isChanged(), is(false)); + + String newNode = "foo"; + if (randomBoolean()) { + builder.addNode(newModel, newNode); + } else { + builder.addFailedNode(newModel, newNode, "failure"); + } + assertThat(builder.isChanged(), is(true)); + + builder = TrainedModelAllocationMetadata.Builder.fromMetadata(builder.build()); + assertThat(builder.isChanged(), is(false)); + + builder.removeNode(newModel, newNode); + assertThat(builder.isChanged(), is(true)); + } + + private static TrainedModelAllocationMetadata.Builder assertUnchanged( + TrainedModelAllocationMetadata.Builder builder, + Function function + ) { + function.apply(builder); + assertThat(builder.isChanged(), is(false)); + return builder; + } + + private static StartTrainedModelDeploymentAction.TaskParams randomParams(String modelId) { + return new StartTrainedModelDeploymentAction.TaskParams(modelId, "test-index", randomNonNegativeLong()); + } +} diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeServiceTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeServiceTests.java new file mode 100644 index 0000000000000..d7c37a120cdb4 --- /dev/null +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/allocation/TrainedModelAllocationNodeServiceTests.java @@ -0,0 +1,359 @@ +/* + * 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.ml.inference.allocation; + +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodeRole; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.tasks.TaskManager; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ScalingExecutorBuilder; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction; +import org.elasticsearch.xpack.core.ml.action.UpdateTrainedModelAllocationStateAction; +import org.elasticsearch.xpack.core.ml.inference.allocation.RoutingState; +import org.elasticsearch.xpack.ml.inference.deployment.DeploymentManager; +import org.elasticsearch.xpack.ml.inference.deployment.TrainedModelDeploymentTask; +import org.junit.After; +import org.junit.Before; +import org.mockito.ArgumentCaptor; + +import java.util.Collections; + +import static org.elasticsearch.xpack.ml.MachineLearning.UTILITY_THREAD_POOL_NAME; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class TrainedModelAllocationNodeServiceTests extends ESTestCase { + + private static final String NODE_ID = "test-node"; + + private ClusterService clusterService; + private DeploymentManager deploymentManager; + private ThreadPool threadPool; + private TrainedModelAllocationService trainedModelAllocationService; + private TaskManager taskManager; + + @Before + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void setupObjects() { + trainedModelAllocationService = mock(TrainedModelAllocationService.class); + clusterService = mock(ClusterService.class); + threadPool = new TestThreadPool( + "TrainedModelAllocationNodeServiceTests", + new ScalingExecutorBuilder(UTILITY_THREAD_POOL_NAME, 1, 4, TimeValue.timeValueMinutes(10), "xpack.ml.utility_thread_pool") + ); + taskManager = new TaskManager(Settings.EMPTY, threadPool, Collections.emptySet()); + deploymentManager = mock(DeploymentManager.class); + doAnswer(invocationOnMock -> { + ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; + listener.onResponse(invocationOnMock.getArguments()[0]); + return null; + }).when(deploymentManager).startDeployment(any(), any()); + doAnswer(invocationOnMock -> { + ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; + listener.onResponse(AcknowledgedResponse.TRUE); + return null; + }).when(trainedModelAllocationService).updateModelAllocationState(any(), any()); + } + + @After + public void shutdown() throws InterruptedException { + terminate(threadPool); + } + + public void testLoadQueuedModels() { + TrainedModelAllocationNodeService trainedModelAllocationNodeService = createService(); + + // When there are no queued models + trainedModelAllocationNodeService.loadQueuedModels(); + verify(deploymentManager, never()).startDeployment(any(), any()); + + String modelToLoad = "loading-model"; + String anotherModel = "loading-model-again"; + + // Should only load each model once + trainedModelAllocationNodeService.prepareModelToLoad(newParams(modelToLoad)); + trainedModelAllocationNodeService.prepareModelToLoad(newParams(modelToLoad)); + trainedModelAllocationNodeService.prepareModelToLoad(newParams(anotherModel)); + + trainedModelAllocationNodeService.loadQueuedModels(); + + ArgumentCaptor taskCapture = ArgumentCaptor.forClass(TrainedModelDeploymentTask.class); + ArgumentCaptor requestCapture = ArgumentCaptor.forClass( + UpdateTrainedModelAllocationStateAction.Request.class + ); + verify(deploymentManager, times(2)).startDeployment(taskCapture.capture(), any()); + verify(trainedModelAllocationService, times(2)).updateModelAllocationState(requestCapture.capture(), any()); + + assertThat(taskCapture.getAllValues().get(0).getModelId(), equalTo(modelToLoad)); + assertThat(requestCapture.getAllValues().get(0).getModelId(), equalTo(modelToLoad)); + assertThat(requestCapture.getAllValues().get(0).getNodeId(), equalTo(NODE_ID)); + assertThat(requestCapture.getAllValues().get(0).getRoutingState().getState(), equalTo(RoutingState.STARTED)); + + assertThat(taskCapture.getAllValues().get(1).getModelId(), equalTo(anotherModel)); + assertThat(requestCapture.getAllValues().get(1).getModelId(), equalTo(anotherModel)); + assertThat(requestCapture.getAllValues().get(1).getNodeId(), equalTo(NODE_ID)); + assertThat(requestCapture.getAllValues().get(1).getRoutingState().getState(), equalTo(RoutingState.STARTED)); + + // Since models are loaded, there shouldn't be any more loadings to occur + trainedModelAllocationNodeService.prepareModelToLoad(newParams(anotherModel)); + trainedModelAllocationNodeService.loadQueuedModels(); + verifyNoMoreInteractions(deploymentManager, trainedModelAllocationService); + } + + public void testLoadQueuedModelsWhenStopped() { + TrainedModelAllocationNodeService trainedModelAllocationNodeService = createService(); + + // When there are no queued models + String modelToLoad = "loading-model"; + + // Should only load each model once + trainedModelAllocationNodeService.prepareModelToLoad(newParams(modelToLoad)); + trainedModelAllocationNodeService.stop(); + + trainedModelAllocationNodeService.loadQueuedModels(); + verifyNoMoreInteractions(deploymentManager, trainedModelAllocationService); + } + + public void testLoadQueuedModelsWhenTaskIsStopped() throws Exception { + TrainedModelAllocationNodeService trainedModelAllocationNodeService = createService(); + + // When there are no queued models + String modelToLoad = "loading-model"; + String stoppedModelToLoad = "stopped-loading-model"; + + // Only one model should be loaded, the other should be stopped + trainedModelAllocationNodeService.prepareModelToLoad(newParams(modelToLoad)); + trainedModelAllocationNodeService.prepareModelToLoad(newParams(stoppedModelToLoad)); + trainedModelAllocationNodeService.getTask(stoppedModelToLoad).stop("testing"); + trainedModelAllocationNodeService.loadQueuedModels(); + + assertBusy(() -> { + ArgumentCaptor stoppedTaskCapture = ArgumentCaptor.forClass(TrainedModelDeploymentTask.class); + verify(deploymentManager, times(1)).stopDeployment(stoppedTaskCapture.capture()); + assertThat(stoppedTaskCapture.getValue().getModelId(), equalTo(stoppedModelToLoad)); + }); + ArgumentCaptor startTaskCapture = ArgumentCaptor.forClass(TrainedModelDeploymentTask.class); + ArgumentCaptor requestCapture = ArgumentCaptor.forClass( + UpdateTrainedModelAllocationStateAction.Request.class + ); + verify(deploymentManager, times(1)).startDeployment(startTaskCapture.capture(), any()); + assertBusy(() -> verify(trainedModelAllocationService, times(3)).updateModelAllocationState(requestCapture.capture(), any())); + + boolean seenStopping = false; + for (int i = 0; i < 3; i++) { + UpdateTrainedModelAllocationStateAction.Request request = requestCapture.getAllValues().get(i); + assertThat(request.getNodeId(), equalTo(NODE_ID)); + if (request.getModelId().equals(stoppedModelToLoad)) { + if (seenStopping) { + assertThat(request.getRoutingState().getState(), equalTo(RoutingState.STOPPED)); + } else { + assertThat(request.getRoutingState().getState(), equalTo(RoutingState.STOPPING)); + seenStopping = true; + } + } else { + assertThat(request.getModelId(), equalTo(modelToLoad)); + assertThat(request.getRoutingState().getState(), equalTo(RoutingState.STARTED)); + } + } + assertThat(startTaskCapture.getAllValues().get(0).getModelId(), equalTo(modelToLoad)); + + verifyNoMoreInteractions(deploymentManager, trainedModelAllocationService); + } + + public void testLoadQueuedModelsWhenOneFails() { + String modelToLoad = "loading-model"; + String failedModelToLoad = "failed-loading-model"; + withLoadFailure(failedModelToLoad); + TrainedModelAllocationNodeService trainedModelAllocationNodeService = createService(); + + trainedModelAllocationNodeService.prepareModelToLoad(newParams(modelToLoad)); + trainedModelAllocationNodeService.prepareModelToLoad(newParams(failedModelToLoad)); + + trainedModelAllocationNodeService.loadQueuedModels(); + + ArgumentCaptor startTaskCapture = ArgumentCaptor.forClass(TrainedModelDeploymentTask.class); + ArgumentCaptor requestCapture = ArgumentCaptor.forClass( + UpdateTrainedModelAllocationStateAction.Request.class + ); + verify(deploymentManager, times(2)).startDeployment(startTaskCapture.capture(), any()); + verify(trainedModelAllocationService, times(2)).updateModelAllocationState(requestCapture.capture(), any()); + + assertThat(startTaskCapture.getAllValues().get(0).getModelId(), equalTo(modelToLoad)); + assertThat(requestCapture.getAllValues().get(0).getModelId(), equalTo(modelToLoad)); + assertThat(requestCapture.getAllValues().get(0).getNodeId(), equalTo(NODE_ID)); + assertThat(requestCapture.getAllValues().get(0).getRoutingState().getState(), equalTo(RoutingState.STARTED)); + + assertThat(startTaskCapture.getAllValues().get(1).getModelId(), equalTo(failedModelToLoad)); + assertThat(requestCapture.getAllValues().get(1).getModelId(), equalTo(failedModelToLoad)); + assertThat(requestCapture.getAllValues().get(1).getNodeId(), equalTo(NODE_ID)); + assertThat(requestCapture.getAllValues().get(1).getRoutingState().getState(), equalTo(RoutingState.FAILED)); + + verifyNoMoreInteractions(deploymentManager, trainedModelAllocationService); + } + + public void testClusterChanged() throws Exception { + final TrainedModelAllocationNodeService trainedModelAllocationNodeService = createService(); + final DiscoveryNodes nodes = DiscoveryNodes.builder() + .localNodeId(NODE_ID) + .add( + new DiscoveryNode( + NODE_ID, + NODE_ID, + buildNewFakeTransportAddress(), + Collections.emptyMap(), + DiscoveryNodeRole.roles(), + Version.CURRENT + ) + ) + .build(); + String modelOne = "model-1"; + String modelTwo = "model-2"; + String notUsedModel = "model-3"; + ClusterChangedEvent event = new ClusterChangedEvent( + "testClusterChanged", + ClusterState.builder(new ClusterName("testClusterChanged")) + .nodes(nodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty() + .addNewAllocation(newParams(modelOne)) + .addNode(modelOne, NODE_ID) + .addNewAllocation(newParams(modelTwo)) + .addNode(modelTwo, NODE_ID) + .addNewAllocation(newParams(notUsedModel)) + .addNode(notUsedModel, "some-other-node") + .build() + ) + .build() + ) + .build(), + ClusterState.EMPTY_STATE + ); + + trainedModelAllocationNodeService.clusterChanged(event); + + event = new ClusterChangedEvent( + "testClusterChanged", + ClusterState.builder(new ClusterName("testClusterChanged")) + .nodes(nodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty() + .addNewAllocation(newParams(modelOne)) + .addNode(modelOne, NODE_ID) + .addNewAllocation(newParams(modelTwo)) + .addNode(modelTwo, "some-other-node") + .addNewAllocation(newParams(notUsedModel)) + .addNode(notUsedModel, "some-other-node") + .build() + ) + .build() + ) + .build(), + ClusterState.EMPTY_STATE + ); + trainedModelAllocationNodeService.clusterChanged(event); + + trainedModelAllocationNodeService.loadQueuedModels(); + + assertBusy(() -> { + ArgumentCaptor stoppedTaskCapture = ArgumentCaptor.forClass(TrainedModelDeploymentTask.class); + verify(deploymentManager, times(1)).stopDeployment(stoppedTaskCapture.capture()); + assertThat(stoppedTaskCapture.getAllValues().get(0).getModelId(), equalTo(modelTwo)); + }); + ArgumentCaptor startTaskCapture = ArgumentCaptor.forClass(TrainedModelDeploymentTask.class); + ArgumentCaptor requestCapture = ArgumentCaptor.forClass( + UpdateTrainedModelAllocationStateAction.Request.class + ); + verify(deploymentManager, times(1)).startDeployment(startTaskCapture.capture(), any()); + verify(trainedModelAllocationService, times(1)).updateModelAllocationState(requestCapture.capture(), any()); + + assertThat(startTaskCapture.getAllValues().get(0).getModelId(), equalTo(modelOne)); + assertThat(requestCapture.getAllValues().get(0).getModelId(), equalTo(modelOne)); + assertThat(requestCapture.getAllValues().get(0).getNodeId(), equalTo(NODE_ID)); + assertThat(requestCapture.getAllValues().get(0).getRoutingState().getState(), equalTo(RoutingState.STARTED)); + + event = new ClusterChangedEvent( + "testClusterChanged", + ClusterState.builder(new ClusterName("testClusterChanged")) + .nodes(nodes) + .metadata( + Metadata.builder() + .putCustom( + TrainedModelAllocationMetadata.NAME, + TrainedModelAllocationMetadata.Builder.empty() + .addNewAllocation(newParams(modelOne)) + .addNode(modelOne, NODE_ID) + .build() + ) + .build() + ) + .build(), + ClusterState.EMPTY_STATE + ); + trainedModelAllocationNodeService.clusterChanged(event); + + trainedModelAllocationNodeService.loadQueuedModels(); + + verifyNoMoreInteractions(deploymentManager, trainedModelAllocationService); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void withLoadFailure(String modelId) { + doAnswer(invocationOnMock -> { + TrainedModelDeploymentTask task = (TrainedModelDeploymentTask) invocationOnMock.getArguments()[0]; + ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; + if (task.getModelId().equals(modelId)) { + listener.onFailure(new ResourceNotFoundException("model node found")); + } else { + listener.onResponse(task); + } + return null; + }).when(deploymentManager).startDeployment(any(), any()); + } + + private static StartTrainedModelDeploymentAction.TaskParams newParams(String modelId) { + return new StartTrainedModelDeploymentAction.TaskParams(modelId, "any-index", randomNonNegativeLong()); + } + + private TrainedModelAllocationNodeService createService() { + return new TrainedModelAllocationNodeService( + trainedModelAllocationService, + clusterService, + deploymentManager, + taskManager, + threadPool, + NODE_ID + ); + } + +} diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index b7036fe0aa9c6..a54b1b98af8a1 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -228,6 +228,9 @@ public class Constants { "cluster:internal/xpack/ml/job/finalize_job_execution", "cluster:internal/xpack/ml/job/kill/process", "cluster:internal/xpack/ml/job/update/process", + "cluster:internal/xpack/ml/model_allocation/create", + "cluster:internal/xpack/ml/model_allocation/delete", + "cluster:internal/xpack/ml/model_allocation/update", "cluster:internal/xpack/ml/reset_mode", "cluster:internal/xpack/transform/reset_mode", "cluster:monitor/allocation/explain", From f898c30dc9056827aff3b373ce51c164a52a6f38 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Tue, 3 Aug 2021 13:15:40 -0700 Subject: [PATCH 4/9] Add ability to allow list instance methods on the script class (#76045) This change adds support to allow list instance methods on the script class in Painless and super classes of the script class. It works the same way as allow listing works now for other classes. --- .../phase/DefaultIRTreeToASMBytesPhase.java | 12 ++ .../phase/DefaultSemanticAnalysisPhase.java | 75 ++++++++----- .../phase/DefaultUserTreeToIRTreePhase.java | 6 + .../painless/symbol/Decorations.java | 13 +++ .../painless/symbol/IRDecorations.java | 13 +++ .../org/elasticsearch/painless/ThisTests.java | 106 ++++++++++++++++++ .../spi/org.elasticsearch.painless.this | 12 ++ 7 files changed, 206 insertions(+), 31 deletions(-) create mode 100644 modules/lang-painless/src/test/java/org/elasticsearch/painless/ThisTests.java create mode 100644 modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.this diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java index 8fc2a377afc43..d265a514a453c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java @@ -142,6 +142,7 @@ import org.elasticsearch.painless.symbol.IRDecorations.IRDSize; import org.elasticsearch.painless.symbol.IRDecorations.IRDStoreType; import org.elasticsearch.painless.symbol.IRDecorations.IRDSymbol; +import org.elasticsearch.painless.symbol.IRDecorations.IRDThisMethod; import org.elasticsearch.painless.symbol.IRDecorations.IRDTypeParameters; import org.elasticsearch.painless.symbol.IRDecorations.IRDUnaryType; import org.elasticsearch.painless.symbol.IRDecorations.IRDValue; @@ -1650,6 +1651,7 @@ public void visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, W methodWriter.writeDebugInfo(irInvokeCallMemberNode.getLocation()); LocalFunction localFunction = irInvokeCallMemberNode.getDecorationValue(IRDFunction.class); + PainlessMethod thisMethod = irInvokeCallMemberNode.getDecorationValue(IRDThisMethod.class); PainlessMethod importedMethod = irInvokeCallMemberNode.getDecorationValue(IRDMethod.class); PainlessClassBinding classBinding = irInvokeCallMemberNode.getDecorationValue(IRDClassBinding.class); PainlessInstanceBinding instanceBinding = irInvokeCallMemberNode.getDecorationValue(IRDInstanceBinding.class); @@ -1669,6 +1671,16 @@ public void visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, W } else { methodWriter.invokeVirtual(CLASS_TYPE, localFunction.getAsmMethod()); } + } else if (thisMethod != null) { + methodWriter.loadThis(); + + for (ExpressionNode irArgumentNode : irArgumentNodes) { + visit(irArgumentNode, writeScope); + } + + Method asmMethod = new Method(thisMethod.javaMethod.getName(), + thisMethod.methodType.dropParameterTypes(0, 1).toMethodDescriptorString()); + methodWriter.invokeVirtual(CLASS_TYPE, asmMethod); } else if (importedMethod != null) { for (ExpressionNode irArgumentNode : irArgumentNodes) { visit(irArgumentNode, writeScope); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java index 769702afe94b3..e387dba4cb2ff 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java @@ -119,6 +119,7 @@ import org.elasticsearch.painless.symbol.Decorations.StandardPainlessMethod; import org.elasticsearch.painless.symbol.Decorations.StaticType; import org.elasticsearch.painless.symbol.Decorations.TargetType; +import org.elasticsearch.painless.symbol.Decorations.ThisPainlessMethod; import org.elasticsearch.painless.symbol.Decorations.TypeParameters; import org.elasticsearch.painless.symbol.Decorations.UnaryType; import org.elasticsearch.painless.symbol.Decorations.UpcastPainlessCast; @@ -1714,6 +1715,7 @@ public void visitCallLocal(ECallLocal userCallLocalNode, SemanticScope semanticS ScriptScope scriptScope = semanticScope.getScriptScope(); FunctionTable.LocalFunction localFunction = null; + PainlessMethod thisMethod = null; PainlessMethod importedMethod = null; PainlessClassBinding classBinding = null; int classBindingOffset = 0; @@ -1728,44 +1730,47 @@ public void visitCallLocal(ECallLocal userCallLocalNode, SemanticScope semanticS localFunction = null; } - if (localFunction != null) { - semanticScope.setUsesInstanceMethod(); - } else { - importedMethod = scriptScope.getPainlessLookup().lookupImportedPainlessMethod(methodName, userArgumentsSize); + if (localFunction == null) { + thisMethod = scriptScope.getPainlessLookup().lookupPainlessMethod( + scriptScope.getScriptClassInfo().getBaseClass(), false, methodName, userArgumentsSize); - if (importedMethod == null) { - classBinding = scriptScope.getPainlessLookup().lookupPainlessClassBinding(methodName, userArgumentsSize); + if (thisMethod == null) { + importedMethod = scriptScope.getPainlessLookup().lookupImportedPainlessMethod(methodName, userArgumentsSize); - // check to see if this class binding requires an implicit this reference - if (classBinding != null && classBinding.typeParameters.isEmpty() == false && - classBinding.typeParameters.get(0) == scriptScope.getScriptClassInfo().getBaseClass()) { - classBinding = null; - } + if (importedMethod == null) { + classBinding = scriptScope.getPainlessLookup().lookupPainlessClassBinding(methodName, userArgumentsSize); - if (classBinding == null) { - // This extra check looks for a possible match where the class binding requires an implicit this - // reference. This is a temporary solution to allow the class binding access to data from the - // base script class without need for a user to add additional arguments. A long term solution - // will likely involve adding a class instance binding where any instance can have a class binding - // as part of its API. However, the situation at run-time is difficult and will modifications that - // are a substantial change if even possible to do. - classBinding = scriptScope.getPainlessLookup().lookupPainlessClassBinding(methodName, userArgumentsSize + 1); - - if (classBinding != null) { - if (classBinding.typeParameters.isEmpty() == false && - classBinding.typeParameters.get(0) == scriptScope.getScriptClassInfo().getBaseClass()) { - classBindingOffset = 1; - } else { - classBinding = null; - } + // check to see if this class binding requires an implicit this reference + if (classBinding != null && classBinding.typeParameters.isEmpty() == false && + classBinding.typeParameters.get(0) == scriptScope.getScriptClassInfo().getBaseClass()) { + classBinding = null; } if (classBinding == null) { - instanceBinding = scriptScope.getPainlessLookup().lookupPainlessInstanceBinding(methodName, userArgumentsSize); + // This extra check looks for a possible match where the class binding requires an implicit this + // reference. This is a temporary solution to allow the class binding access to data from the + // base script class without need for a user to add additional arguments. A long term solution + // will likely involve adding a class instance binding where any instance can have a class binding + // as part of its API. However, the situation at run-time is difficult and will modifications that + // are a substantial change if even possible to do. + classBinding = scriptScope.getPainlessLookup().lookupPainlessClassBinding(methodName, userArgumentsSize + 1); + + if (classBinding != null) { + if (classBinding.typeParameters.isEmpty() == false && + classBinding.typeParameters.get(0) == scriptScope.getScriptClassInfo().getBaseClass()) { + classBindingOffset = 1; + } else { + classBinding = null; + } + } + + if (classBinding == null) { + instanceBinding = scriptScope.getPainlessLookup().lookupPainlessInstanceBinding(methodName, userArgumentsSize); - if (instanceBinding == null) { - throw userCallLocalNode.createError(new IllegalArgumentException( - "Unknown call [" + methodName + "] with [" + userArgumentNodes + "] arguments.")); + if (instanceBinding == null) { + throw userCallLocalNode.createError(new IllegalArgumentException( + "Unknown call [" + methodName + "] with [" + userArgumentNodes + "] arguments.")); + } } } } @@ -1775,10 +1780,18 @@ public void visitCallLocal(ECallLocal userCallLocalNode, SemanticScope semanticS List> typeParameters; if (localFunction != null) { + semanticScope.setUsesInstanceMethod(); semanticScope.putDecoration(userCallLocalNode, new StandardLocalFunction(localFunction)); typeParameters = new ArrayList<>(localFunction.getTypeParameters()); valueType = localFunction.getReturnType(); + } else if (thisMethod != null) { + semanticScope.setUsesInstanceMethod(); + semanticScope.putDecoration(userCallLocalNode, new ThisPainlessMethod(thisMethod)); + + scriptScope.markNonDeterministic(thisMethod.annotations.containsKey(NonDeterministicAnnotation.class)); + typeParameters = new ArrayList<>(thisMethod.typeParameters); + valueType = thisMethod.returnType; } else if (importedMethod != null) { semanticScope.putDecoration(userCallLocalNode, new StandardPainlessMethod(importedMethod)); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java index 1c004c985b5b2..d67cc59b95045 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java @@ -185,6 +185,7 @@ import org.elasticsearch.painless.symbol.Decorations.StandardPainlessMethod; import org.elasticsearch.painless.symbol.Decorations.StaticType; import org.elasticsearch.painless.symbol.Decorations.TargetType; +import org.elasticsearch.painless.symbol.Decorations.ThisPainlessMethod; import org.elasticsearch.painless.symbol.Decorations.TypeParameters; import org.elasticsearch.painless.symbol.Decorations.UnaryType; import org.elasticsearch.painless.symbol.Decorations.UpcastPainlessCast; @@ -239,6 +240,7 @@ import org.elasticsearch.painless.symbol.IRDecorations.IRDSize; import org.elasticsearch.painless.symbol.IRDecorations.IRDStoreType; import org.elasticsearch.painless.symbol.IRDecorations.IRDSymbol; +import org.elasticsearch.painless.symbol.IRDecorations.IRDThisMethod; import org.elasticsearch.painless.symbol.IRDecorations.IRDTypeParameters; import org.elasticsearch.painless.symbol.IRDecorations.IRDUnaryType; import org.elasticsearch.painless.symbol.IRDecorations.IRDValue; @@ -1221,6 +1223,10 @@ public void visitCallLocal(ECallLocal callLocalNode, ScriptScope scriptScope) { if (scriptScope.hasDecoration(callLocalNode, StandardLocalFunction.class)) { LocalFunction localFunction = scriptScope.getDecoration(callLocalNode, StandardLocalFunction.class).getLocalFunction(); irInvokeCallMemberNode.attachDecoration(new IRDFunction(localFunction)); + } else if (scriptScope.hasDecoration(callLocalNode, ThisPainlessMethod.class)) { + PainlessMethod thisMethod = + scriptScope.getDecoration(callLocalNode, ThisPainlessMethod.class).getThisPainlessMethod(); + irInvokeCallMemberNode.attachDecoration(new IRDThisMethod(thisMethod)); } else if (scriptScope.hasDecoration(callLocalNode, StandardPainlessMethod.class)) { PainlessMethod importedMethod = scriptScope.getDecoration(callLocalNode, StandardPainlessMethod.class).getStandardPainlessMethod(); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java index bce418be4ce12..de6f748928870 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java @@ -417,6 +417,19 @@ public LocalFunction getLocalFunction() { } } + public static class ThisPainlessMethod implements Decoration { + + private final PainlessMethod thisPainlessMethod; + + public ThisPainlessMethod(PainlessMethod thisPainlessMethod) { + this.thisPainlessMethod = Objects.requireNonNull(thisPainlessMethod); + } + + public PainlessMethod getThisPainlessMethod() { + return thisPainlessMethod; + } + } + public static class StandardPainlessClassBinding implements Decoration { private final PainlessClassBinding painlessClassBinding; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java index 303d5d3506f5e..f9e76e5317b17 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java @@ -370,6 +370,19 @@ public IRDFunction(LocalFunction value) { } } + /** describes a method for a node on the script class; which method depends on node type */ + public static class IRDThisMethod extends IRDecoration { + + public IRDThisMethod(PainlessMethod value) { + super(value); + } + + @Override + public String toString() { + return PainlessLookupUtility.buildPainlessMethodKey(getValue().javaMethod.getName(), getValue().typeParameters.size()); + } + } + /** describes the call to a class binding */ public static class IRDClassBinding extends IRDecoration { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ThisTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ThisTests.java new file mode 100644 index 0000000000000..1116c17c83190 --- /dev/null +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ThisTests.java @@ -0,0 +1,106 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.painless.spi.Whitelist; +import org.elasticsearch.painless.spi.WhitelistLoader; +import org.elasticsearch.script.ScriptContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ThisTests extends ScriptTestCase { + + public abstract static class ThisBaseScript { + + protected String baseString; + + public ThisBaseScript(String baseString) { + this.baseString = baseString; + } + + public String getBaseString() { + return baseString; + } + + public void setBaseString(String testString) { + this.baseString = testString; + } + + public int getBaseLength() { + return baseString.length(); + } + } + + public abstract static class ThisScript extends ThisBaseScript { + + protected String thisString; + + public ThisScript(String baseString, String thisString) { + super(baseString); + + this.thisString = thisString; + } + + public String thisString() { + return thisString; + } + + public void thisString(String testString) { + this.thisString = testString; + } + + public int thisLength() { + return thisString.length(); + } + + public abstract Object execute(); + + public interface Factory { + + ThisScript newInstance(String baseString, String testString); + } + + public static final String[] PARAMETERS = {}; + public static final ScriptContext CONTEXT = + new ScriptContext<>("this_test", ThisScript.Factory.class); + } + + @Override + protected Map, List> scriptContexts() { + Map, List> contexts = new HashMap<>(); + List whitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS); + whitelists.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.painless.this")); + contexts.put(ThisScript.CONTEXT, whitelists); + return contexts; + } + + public Object exec(String script, String baseString, String testString) { + ThisScript.Factory factory = scriptEngine.compile(null, script, ThisScript.CONTEXT, new HashMap<>()); + ThisScript testThisScript = factory.newInstance(baseString, testString); + return testThisScript.execute(); + } + + public void testThisMethods() { + assertEquals("basethis", exec("getBaseString() + thisString()", "base", "this")); + assertEquals(8, exec("getBaseLength() + thisLength()", "yyy", "xxxxx")); + + List result = new ArrayList<>(); + result.add("this"); + result.add("base"); + assertEquals(result, exec("List result = []; " + + "thisString('this');" + + "setBaseString('base');" + + "result.add(thisString()); " + + "result.add(getBaseString());" + + "result;", "", "")); + } +} diff --git a/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.this b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.this new file mode 100644 index 0000000000000..fb5eedf3388c9 --- /dev/null +++ b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.this @@ -0,0 +1,12 @@ +class org.elasticsearch.painless.ThisTests$ThisBaseScript @no_import { + String getBaseString(); + void setBaseString(String); + int getBaseLength(); +} + + +class org.elasticsearch.painless.ThisTests$ThisScript @no_import { + String thisString(); + void thisString(String); + int thisLength(); +} From feed41e8710102d38ab54f489789be42fba556cc Mon Sep 17 00:00:00 2001 From: Mark Vieira Date: Tue, 3 Aug 2021 14:40:35 -0700 Subject: [PATCH 5/9] Add GitHub action workflow for auto-backport-and-merge label --- .github/workflows/backport-and-merge.yml | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/backport-and-merge.yml diff --git a/.github/workflows/backport-and-merge.yml b/.github/workflows/backport-and-merge.yml new file mode 100644 index 0000000000000..31ff53f8d3d8c --- /dev/null +++ b/.github/workflows/backport-and-merge.yml @@ -0,0 +1,40 @@ +name: "Auto Backport and Merge" +on: + pull_request_target: + branches: + - master + types: + - labeled + - closed + +jobs: + backport: + name: Backport PR + if: | + github.event.pull_request.merged == true + && contains(github.event.pull_request.labels.*.name, 'auto-backport-and-merge') + && ( + (github.event.action == 'labeled' && github.event.label.name == 'auto-backport-and-merge') + || (github.event.action == 'closed') + ) + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'elastic/kibana-github-actions' + ref: main + path: ./actions + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Run Backport + uses: ./actions/backport + with: + github_token: ${{secrets.ELASTICSEARCHMACHINE_TOKEN}} + commit_user: elasticsearchmachine + commit_email: elasticsarchmachine@users.noreply.github.com + target_pr_labels: 'backport, auto-merge' + auto_merge: 'false' + manual_backport_command_template: 'backport --pr %pullNumber%' From f9e8849991998b0e76cb4f638ecd16d3a23979b3 Mon Sep 17 00:00:00 2001 From: Mark Vieira Date: Tue, 3 Aug 2021 16:17:41 -0700 Subject: [PATCH 6/9] Consolidate GitHub action backport configuration --- .github/workflows/backport-and-merge.yml | 40 ------------------------ .github/workflows/backport.yml | 30 ++++++++++++++++++ 2 files changed, 30 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/backport-and-merge.yml diff --git a/.github/workflows/backport-and-merge.yml b/.github/workflows/backport-and-merge.yml deleted file mode 100644 index 31ff53f8d3d8c..0000000000000 --- a/.github/workflows/backport-and-merge.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: "Auto Backport and Merge" -on: - pull_request_target: - branches: - - master - types: - - labeled - - closed - -jobs: - backport: - name: Backport PR - if: | - github.event.pull_request.merged == true - && contains(github.event.pull_request.labels.*.name, 'auto-backport-and-merge') - && ( - (github.event.action == 'labeled' && github.event.label.name == 'auto-backport-and-merge') - || (github.event.action == 'closed') - ) - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v2 - with: - repository: 'elastic/kibana-github-actions' - ref: main - path: ./actions - - - name: Install Actions - run: npm install --production --prefix ./actions - - - name: Run Backport - uses: ./actions/backport - with: - github_token: ${{secrets.ELASTICSEARCHMACHINE_TOKEN}} - commit_user: elasticsearchmachine - commit_email: elasticsarchmachine@users.noreply.github.com - target_pr_labels: 'backport, auto-merge' - auto_merge: 'false' - manual_backport_command_template: 'backport --pr %pullNumber%' diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 85323c08815a6..3b987b10b2de6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -37,3 +37,33 @@ jobs: commit_email: elasticsarchmachine@users.noreply.github.com auto_merge: 'false' manual_backport_command_template: 'backport --pr %pullNumber%' + backport_and_merge: + name: Backport and Merge PR + if: | + github.event.pull_request.merged == true + && contains(github.event.pull_request.labels.*.name, 'auto-backport-and-merge') + && ( + (github.event.action == 'labeled' && github.event.label.name == 'auto-backport-and-merge') + || (github.event.action == 'closed') + ) + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'elastic/kibana-github-actions' + ref: main + path: ./actions + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Run Backport + uses: ./actions/backport + with: + github_token: ${{secrets.ELASTICSEARCHMACHINE_TOKEN}} + commit_user: elasticsearchmachine + commit_email: elasticsarchmachine@users.noreply.github.com + target_pr_labels: 'backport, auto-merge' + auto_merge: 'false' + manual_backport_command_template: 'backport --pr %pullNumber%' From bba0f6c4b1e6a94a6de1341398e65d5c26dcfa9a Mon Sep 17 00:00:00 2001 From: Ignacio Vera Date: Wed, 4 Aug 2021 07:58:28 +0200 Subject: [PATCH 7/9] Speed up creation of vector tiles features (#75874) * Simplify the shape when defined in spherical mercator projection instead of when defined in pixel space. --- .../vectortile/feature/FeatureFactory.java | 35 ++++++++++--- .../feature/FeatureFactoryTests.java | 49 ++++++++++++++++++ .../xpack/vectortile/feature/polygon.wkt.gz | Bin 0 -> 351785 bytes 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugin/vector-tile/src/test/resources/org/elasticsearch/xpack/vectortile/feature/polygon.wkt.gz diff --git a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java index 0fab0ce78f4b7..a3b8349e15998 100644 --- a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java +++ b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java @@ -34,6 +34,7 @@ import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.simplify.TopologyPreservingSimplifier; import java.util.ArrayList; import java.util.List; @@ -57,15 +58,21 @@ public FeatureFactory(int z, int x, int y, int extent) { final Rectangle r = SphericalMercatorUtils.recToSphericalMercator(GeoTileUtils.toBoundingBox(x, y, z)); this.tileEnvelope = new Envelope(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY()); this.clipEnvelope = new Envelope(tileEnvelope); - this.clipEnvelope.expandBy(tileEnvelope.getWidth() * 0.1d, tileEnvelope.getHeight() * 0.1d); - this.builder = new JTSGeometryBuilder(geomFactory); + // pixel precision of the tile in the mercator projection. + final double pixelPrecision = 2 * SphericalMercatorUtils.MERCATOR_BOUNDS / ((1L << z) * extent); + this.clipEnvelope.expandBy(pixelPrecision, pixelPrecision); + this.builder = new JTSGeometryBuilder(geomFactory, geomFactory.toGeometry(tileEnvelope), pixelPrecision); // TODO: Not sure what is the difference between extent and tile size? this.layerParams = new MvtLayerParams(extent, extent); } public List getFeatures(Geometry geometry) { + final org.locationtech.jts.geom.Geometry jtsGeometry = geometry.visit(builder); + if (jtsGeometry.isValid() == false) { + return List.of(); + } final TileGeomResult tileGeom = JtsAdapter.createTileGeom( - JtsAdapter.flatFeatureList(geometry.visit(builder)), + JtsAdapter.flatFeatureList(jtsGeometry), tileEnvelope, clipEnvelope, geomFactory, @@ -82,8 +89,12 @@ public List getFeatures(Geometry geometry) { private static class JTSGeometryBuilder implements GeometryVisitor { private final GeometryFactory geomFactory; + private final org.locationtech.jts.geom.Geometry tile; + private final double pixelPrecision; - JTSGeometryBuilder(GeometryFactory geomFactory) { + JTSGeometryBuilder(GeometryFactory geomFactory, org.locationtech.jts.geom.Geometry tile, double pixelPrecision) { + this.pixelPrecision = pixelPrecision; + this.tile = tile; this.geomFactory = geomFactory; } @@ -152,16 +163,26 @@ private LineString buildLine(Line line) { @Override public org.locationtech.jts.geom.Geometry visit(Polygon polygon) throws RuntimeException { - return buildPolygon(polygon); + final org.locationtech.jts.geom.Polygon jtsPolygon = buildPolygon(polygon); + if (jtsPolygon.contains(tile)) { + // shortcut, we return the tile + return tile; + } + return TopologyPreservingSimplifier.simplify(jtsPolygon, pixelPrecision); } @Override public org.locationtech.jts.geom.Geometry visit(MultiPolygon multiPolygon) throws RuntimeException { final org.locationtech.jts.geom.Polygon[] polygons = new org.locationtech.jts.geom.Polygon[multiPolygon.size()]; for (int i = 0; i < multiPolygon.size(); i++) { - polygons[i] = buildPolygon(multiPolygon.get(i)); + final org.locationtech.jts.geom.Polygon jtsPolygon = buildPolygon(multiPolygon.get(i)); + if (jtsPolygon.contains(tile)) { + // shortcut, we return the tile + return tile; + } + polygons[i] = jtsPolygon; } - return geomFactory.createMultiPolygon(polygons); + return TopologyPreservingSimplifier.simplify(geomFactory.createMultiPolygon(polygons), pixelPrecision); } private org.locationtech.jts.geom.Polygon buildPolygon(Polygon polygon) { diff --git a/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java b/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java index 6ed5ebef5fb7f..bde9e285eda6c 100644 --- a/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java +++ b/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java @@ -20,16 +20,24 @@ import org.elasticsearch.geometry.Point; import org.elasticsearch.geometry.Polygon; import org.elasticsearch.geometry.Rectangle; +import org.elasticsearch.geometry.utils.StandardValidator; +import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils; import org.elasticsearch.test.ESTestCase; import org.hamcrest.Matchers; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import java.util.zip.GZIPInputStream; public class FeatureFactoryTests extends ESTestCase { @@ -151,4 +159,45 @@ private MultiPolygon buildMultiPolygon(Rectangle r) { private GeometryCollection buildGeometryCollection(Rectangle r) { return new GeometryCollection<>(List.of(buildPolygon(r), buildLine(r))); } + + public void testStackOverflowError() throws IOException, ParseException { + // The provided polygon contains 49K points and we have observed that for some tiles and some extent values, + // it makes the library we are using to compute features to fail with a StackOverFlowError. This test just makes + // sure the fix in place avoids that error. + final InputStream is = new GZIPInputStream(getClass().getResourceAsStream("polygon.wkt.gz")); + final BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + final Geometry geometry = WellKnownText.fromWKT(StandardValidator.instance(true), true, reader.readLine()); + for (int i = 0; i < 10; i++) { + final int z = randomIntBetween(0, 4); + final int x = randomIntBetween(0, (1 << z) - 1); + final int y = randomIntBetween(0, (1 << z) - 1); + final int extent = randomIntBetween(128, 8012); + final FeatureFactory builder = new FeatureFactory(z, x, y, extent); + try { + builder.getFeatures(geometry); + } catch (StackOverflowError error) { + fail("stackoverflow error thrown at " + z + "/" + x + "/" + y + "@" + extent); + } + } + } + + public void testTileInsidePolygon() throws Exception { + final int z = randomIntBetween(0, 4); + final int x = randomIntBetween(0, (1 << z) - 1); + final int y = randomIntBetween(0, (1 << z) - 1); + final int extent = randomIntBetween(128, 8012); + final FeatureFactory builder = new FeatureFactory(z, x, y, extent); + final Rectangle rectangle = GeoTileUtils.toBoundingBox(x, y, z); + final double minX = Math.max(-180, rectangle.getMinX() - 1); + final double maxX = Math.min(180, rectangle.getMaxX() + 1); + final double minY = Math.max(-GeoTileUtils.LATITUDE_MASK, rectangle.getMinY() - 1); + final double maxY = Math.min(GeoTileUtils.LATITUDE_MASK, rectangle.getMaxY() + 1); + Polygon bigPolygon = new Polygon( + new LinearRing(new double[] { minX, maxX, maxX, minX, minX }, new double[] { minY, minY, maxY, maxY, minY }) + ); + final List bytes = builder.getFeatures(bigPolygon); + assertThat(bytes, Matchers.iterableWithSize(1)); + final VectorTile.Tile.Feature feature = VectorTile.Tile.Feature.parseFrom(bytes.get(0)); + assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON)); + } } diff --git a/x-pack/plugin/vector-tile/src/test/resources/org/elasticsearch/xpack/vectortile/feature/polygon.wkt.gz b/x-pack/plugin/vector-tile/src/test/resources/org/elasticsearch/xpack/vectortile/feature/polygon.wkt.gz new file mode 100644 index 0000000000000000000000000000000000000000..5137ad4e93e889b9d69bef4d07110fb1af3ec4cd GIT binary patch literal 351785 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

b|*?GUis^gI9d4= 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%0A0-oo#`c$w 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�w%7BcTGn}**3Zux~4%h2Y>Rix}U?3tmgPz4h0V4nM zWt()hICY0S&4I44185pXwL}(Wb6YBwj(T@;O6ZOCXF(Ew(uPGGrDS0Ll$L`NO~`uF z7#N4%y}w)!temZ6NJjUe4O^Mbjjl1d!G1Aq3fFeu_RK(Y8FJVZf07b03M)zF6{W@~ zth$rQ0E2bBe2{P+>4A&Ru`*>w-IY7(;zOl`7uEs9p*+X|bES3UUL3#1R%?W6K1RgL zo7ZhSnY=_!jfoQ8tW2zpYBSF53>-U^|A7GMGX|?k@$J-5JL4sxf`BmToVY?a5%fy_ z%*9$Vl34_h9tP2~9m-B>crRw7JR9QPT~rV~Lu^rw31Z?_VO#4q2A#Nu)E@(+bdD_2 zKkNo^Rr-joHQ!%dhW}q#250TcX7tVsPf%C-zv73%iZLh|&dP;m_~;XdPuA!(8+ z$iS3N*3ReA=QE6iLQgl&MBmOdz(BmZbD^O@d2WFsEH1X){l%g+lvq6Ba9{d^(+fWQY5j+ zC5o;@AqN;0Z9sWBDi|GP>oRg2m*qCazR-o)c2@d38AA%sN)OQkFL$v)G!)WGjO-Sk zIFD|mf+AMSrI2Y5PaaG8{sv~<3yVwR@Z6d(n|bqeKjcf>9C(U%w7?+4 zN8u>h-0RhQ(&VmK&u_e5$zS3id(Nv;%(v1-&{(B8$*|7O!6LVFA~-j}4hqZUV;^be z*DvTc>hV5uJWQg(_-Ou(p`4-ohNC-SnNdf%cXPzma1wN`_!w zzqf?8=8sW4Q`3KhiUfqukq-yqBkR9x0J1iA~hh-JTv zBjZ6&h45_5L+9zNNM8nQrS@7}nJ)T^V^z{fgX($OC+^&!B@Gf@6A-_47l2 zS>g4#ceOgs(aU%18?}LIhHBM56D90#J%(DN4Km`#UP!%S<~ADZ+Cw8UYFpeM3xiS7 zQyLv((BYfL;J84Szose8Ah2>qqL_1=*pZFalaWsMF3^_2WfF%kC_oKqJNF<+ zK=ndwFHS91)64Y;x@XUNF=q~?SDxbC7O$0Sp*|_ITOC1B<*`Mn-@c?qW^M^(Y47iUYTPUGXk`dS5sVmCir#|Wz5CX z%C{~&Wb(dct7@IQ1*`AuL!3fxL3FLtf=NDL#fepFz{PP9RB@D^7hA8=-?~|E;~PD8 zwo&%#^qhQNmLIAVu^+greQg3I_v zVN}Rajsl0s(mRcw6})BFWr^2~j!dp6^h|aNd|k(8aw|qlSrfK9R_;vi7u~Ez4`=pP z&hidA(g*iiFLK^P9T%R;ts##l{BO{$DuqpkDb;0nlJivJxXZJYW#7cTv2`}7!kj?+ z+K`(5?s0?kM(%+Eq`Lll!l z&TaZ3%0c$#jHeG^&%>@=@>j^6Fjt+Ju8r=_$DV${D4OrLLh)^yH@B|z6NC-*#@*L5 zPeh2Rb0&&LU1Te?O#T`En%PnRxzV=*OK8JL*G<_kb4NA8oc83_kj}6Fz(l!sw)8Q% zdoNC!`%8iG4NgWg>~^?+W!0wM#sL(!6)R-dO||VJa*3awG5@`Vh32G@9BV5ADAx_C zNVY=sJP1t;M6}jXg6CO04DP(t#cSer;Rt!|GXxl{musM=qpP^sCm=2`(sZ*#Wiv2{ zW1>JPPPXhGn+=Kidy9}Iw<)=Z6hm+pTN>}8tq9D^`Qd))O~NywEKb0q6k;0`20w-Q z+$`$7C;zThL3zK(-6OxX(dWexY`>cG`~JPZ5vV@Cl$wSUSWcrGG~WYtk%d5pMx^|j z`sO3TUhW}Sy#;dqHHrcvMC_luVzkfwN3NrQ}(Im@4+^LNhl%#NfOrg)98?uT=1P$5==aT0Fhx=?iDWua$ zF5?Kmgu`2W2J(B(aW z?6>kQ${t1I@v(UAxnYkegHGT-E8`PqP%Eg-L@37VzaDtBq(aN97hRlmb&R4mpFt(a z;VQy>@KU= zM9RwX)+l+9@8e*RJ&eaewT(Nk3`-^?tH;JwUpO&#Cc(f}_iiNM- z2%tMJ0mW?hBHr4Wcie}HV9O6saR?`9=OsKJAdjm#m{>l=9i4#QV9!5ksCba;|0ff&B>k9EAFgUsTh$}8ai2p zw#|GUPb$D!QFkGiv|?lPm4cWcHw`2L`Nn$?6zYQ5zj(DPjy<0UlaT8dj8AI^Q6OR& zsQK>4-%Hdz`NrVRx_hN`+76*CSAHkIC!C9(@{{fGO5aAIMZHbe7afG2m8H$E+KnqC_Ju}Jgm)v5NOs3_mwhAZf@d+op|WD;aHas z#WFfCFGW4*+K1L!_}19wMGbVM-b2oq+vrL)X*}+#+og9nD9nE2Wk>=&JyCH=edsEF zW+|n~?Wv+PtB3M#Zr-I!PH`XFOJf?Ao~TA^>L}Kl%k33kbn2D2o1i$51n)|8vN{y9 zpS$*L=!VWBs&4Pb`W1y?j z&ZC0g+g$twUJFwan9*f!Gf~vDz{NM%4a&RK=(XFtEO?by#wDgvd5sI&!HeK8%Gs6% z(qm4nKzklaGF%+cjn+U69-gv28uBIt69N}2R}x90opV|Dz{qOZ<{=3+s?|f%=rSkg z`xtV{a}+Qdk`zXcVJ=0XDRrOeTT51jR91`7$-{8ch$e-bNB5k?Q66-^Ag~oF?7-;n#i(~#qFJrTLJW>H`8A8z0tw9! z4Qke!(Z^!w_J&aIPppo5K_AeT?i9t>i-Lqp#~ETW;%}}$P?sSUe60HqZ7o|{E`A=O zd0=Qd^hioIx@j01+555xfPOTf51kGm3iRTkSY!cmhvN@!N4@$!4YX6TI{lb++i5}<4w)_XtIkKy}tZ~rgK0@4>QpO z$I1$NI$muIW-kE&&R_AyWNzeqVlZ|jJxfPm#fEiSTRNUD=UDq8wBn7SgP1A53ug*R zLnBe#YDA?=A4)o2$fye{O%~|Puc>d;aBj}!WkcLtx9yf}7+%st>6LJbd4W?#VXY~8 z^SIU*$d&zNpNcL4-@MJFOA$rUb@E&1;t?WORHH-w&QvgH!RW*Sh`@Dw8082?*;w!X z+8<99$?k5L%y`+{dtGm=t3_D?e}`OpvlBC?eI1yq6g=jSsfnzGZo5ICbKoZOlv+j? z%u_U*UVoE)FswY~QrA4eu_ABfP3v?hG*`r^^xboSK7l&^PaXVCY(=eHKvJ%8Nj zDekqIHPS53R5uZ zir&x;yE;!j2)`(lI8X;eE?qxm=i)BV!{6S_723AZM3)S@ZSV@TK^amIMWDBe&FU!WJ z?6!+hpey4JBvgAG#0bOJ4hyf>nbnTTF6m!#g%7olw9rnNZrrZEs$0*H8+nPk1*709 zuFQW2RC(q={j_O=H;6!+8OBpB)~sN9(zMzL;M!apO)hsCL{_V^Avjv`a?yl&LBi%Tz0j_avp;#M!8)X zaYCo0UCFswv-A62cCHU-ktBQNx;u>!L3s-890Wp*7nGPI#)ELRCO$TQEsi!Hj`E0L zDNJ~5UKVF6*suV%Qyhk>qQk;uQ*=LNSI^jcxZVC%`+Gb`3t&8r*T~?gkZV|zrUoJVZNvVkgKBftlmL?=Z_8bOJ$)mxCgBUjQN%1q7+bw<2cvgnT} zq+mr-weW9>?B$;P7}eI^e+}EdJ(Y8n(G>@t853HaEQW>4oY1USLD--Ly3$=Day@wpfsI@{}E&bwL+1LZgSaB*S zp&SV#1ee9v27$5T1u=hUEy+^W9gz}XM^G5rD;|S7q17L9>36k?y>-K)yO!hn%LZ!{ z$77+8J&lR3p|?z$BF|eNDk0+XzKmu&l2I{Ob3*qv8gnjRbT?P83O+Vj%wxeaHeSjK zm)(Uk|IyvJUeQW#P*^^O@~iT-RTJH2#Mak{M3P+6e{EPWmZk_6MY zv&}!MufcU1LK265phy4;ejnLhcqGK7kS!zlL!KWud)DFe8&0eG#-=8^b1J8;1t?g10(BMutuf-|W}()KYIT-|Xx20Tj%<$%to*}3 z7aALR_HIzcG#PD>WQ$XS;eJFWMu$@=X^khJrxG}}?3eG&kV&v|DW|FrtC>>Cn?;Il zbX;;t|HdB-%g)&~-c5L@4LSYUJ%43Zt2@LxI8Xd#d*aUGmbBy%#p);}sy&RPs4noDxX}^u*OS#V__yF)*&LL#ZdqW6I6Su$yQ> z@38Ef&oGUGe>xn0K8BdRj4iJjVX(DNPxu(g)$Y;L9~j2XQX?;ZnA4~E{$8jgnDgMh zw8x%L{L|kZ?ZLAH_7KDAputuKJ+2mG)9X39F!K}M=rXNmC#*Mti`O6_8>a~ZhB=1Ni+>-6Y5W@1RFn`F*^i`)csQziSi~n1Wj3|=&V(GIRwPqqleum7C>X$nTM2$oQd2fo*s%w z0z)Y-!tzXG@OcG^%$4J8Wx2TUD91uu7n@$R%E#Wi*%tg6m7p@0tAn$I(+Foj9cR zun*Eu)?lx{f-8ptDx7yl=@g7%B3HH(W6C;`>v@8)RXd&ovZHU-xF1y^037y*edU#c z{&HyeyxBaer42ejc?1L!u&fS+t{mk<4RX%qeP4n&(?s3Drp?ah&cq zFl5DO%6Ek;I+S!C$BK=>9Dl0jxjH4G|DbI{cwn$XPemzym{PME1L--QwRudEur5Au z+z{nj0=u4{b+d7Was-Rs3E70QYM`D`R(WG52cUYr*Z3Oq<69-LMedW38eHGuM4H^0 zj}@Hh-d-vfU3GlcTSaLFXx{d4UaX_?5BCUMZiczcN;6xU$!8oPUoX=7Fmc6KVi6YJ zbVOEQm&G$JA?OkyZ3)r7vyrrD65TDWQD#^%eTcl}SO9U-x+TfoKV|$d#?77jARS=T z*X?#L+ua3Yeq^OV?^IoEVegqeu}#{7z?w&3L)WjoiNLg%@U%dOOOl|qyE$RsRW7%v?8 z!?u|*rb2O1MO%d_@?#H6jHnOCo)6!6B7Bm>lp6{eSe5V?jzvc>gx(2nV6N@-{<*@2HhIxcQ~5a7zYKvUIa9HX%YBD|<0_W_r0)x7M$> zo*|9JrDxr}h&O}lJ9gfh8gDlbwnnjElcmPiLZz!>rxhz*rhE*TT-)iUIPkrI;xzf1 zq9x)9Z|1;}=lo>jdXs9ql2XaGy!jRo%i9OKxC9d$VB7?7(!VU{iFr`wB%6a0(7VMm zk25G|L@Uk?YRg+A&Q5yPtUD7`If~fo7uuYehWJSk#%iWdV#4P+nPJGT2Wkq&seP}% zd(X*v{E8{&39R^TeQC;BW*&kNc|0uQN%U{!S7Gea;wkSd7NYKize)zrClqFC47HB5 zxBf&7q)jm0k+Jdy@d+&TpfTho`33m1n<*JO8kkABI?;N{1GWG-RNl&I8pTc@_6)YwhoU(kI$QpJLcoSa>62Ue{okY*aqw zA3DrUrPFgm^=cMP5qM?fi3t#%ag4YT6ACuGF0swCkHf#9;&ThV;nuI?0f#gB%*9NtDxxO%*m)Jaf1&u0jqd@ zDvPhld6dzKkDhIDD^PpXta>4Jnb!i%YSyV7CA7JQoF42T8%X``NMI!w{Az0rcE~vJ zzSC5B4`@rA+5W9f-3r$GG)*$3I-qo5;*|}=G%(WGQp^GN3Snyh{^_)rzf2a6!i$CVROt_I4ZAr7!UtE($sihzK7hU+MdRwqdi1BIoS@rPO21 zdskeVpYXht@5`~0t&qriz4Y>5S^1W`0wq@joF?X6U%JB&eP_pqzQltcy06*qmUsia zyG7dmNt^N;_Pu%PBTD{I>0@0(y|%+Hy&7GEuB1=qTJL@Efm&MMAigv>F1lfZj-^!` zyBB+xh{8Y@yh94(rH02&Xq0qOGFiioQxp#QbwYlOK2uAG%07^Fqni|}Xv=+LF6M!6 zdgE|5%Ta(lWsmrZ-7`5izTYdd$QC?>H~6sL;)eR(3X)Uooi5H9E0AUIa$MDxMdEue z$O?9*YdAIz%ZPoBi>_``28FzrM5|*J#ee0Ls{8R7eNBin!rTLr_#DL@=ymu)pxx;N zmyFWiOF=TDA4{^@*)4iMN8@j7wi3;^50y>GUyGahdxCD=GM@;v6o2A+&&z!Cp)cvn zhlcdxU8{^0-&ww8hVcm}o|Nyv5GR=3Q)6gsb_uZ9DzqL=)(46@{u(4xOkDoWD*B~x z$ZwU$RcDLiQo6y<Abj2fI(O%(1Ut33%$lWr_=m3=Er zA8S8!(k>;fH`<7m66(kNda8(-sCTvaoxbZ&sHN6*GiO1d%dE#sz9RG2GOwFwf8=Y^ zucjqq&ZJ&jk(Kz{M3Zk6nj5BIHL9(40#TZ?@m($qt7rKqEyR>JzaL_SbTg!CA=s)c5!WPCTcpIE2}N{% zhk2mKRxzzcTe5bU$|LWU+ie@&;xeuVuhmjg@f`m?#JS^k?P<WvcwcMsXWyi0gE#JcJ!MfAG z?M~h-JDz8@D(2nOa;twUKWSr*AJxU=#%z*Ii#L<$R}Rn*n~kM*=RxOTAdJQ~0HTJ7}Xn3Q2R(r~`? zUf;Mh+il&U}@v<18}bq4eFjwh2&Q&S*p9lW^p|!%tC;DWhx$BO~XQ z7wjkQ>MX8+tLH~PgmlO{F24h8I{;rmpuc;xGRAw?$=~$rsZ!qgm(wQcz$~0^(bXMe zc9Gm259_T&x?rXc6QijYsEA74lJ1Br1uk~Cr8)d>>j(aB2)kYaiu(5aD2no{@kO&5 zlLy}iiUO6A!-<#V?_$3Yg*r`FDo&vf{Iaor=u2z$p?XRgwEK>Dl#1&dCN&rByEQhm!<9^mtEqK-UfrG(vpds5>nCfOEh{32G@`e1eTn zmCh+Oq^WeX3MwQept5h&*6U(WwjwQ^Z;cw4`Ug@yBEz0_n$BnPJ{u~?)Haq9& zdg_dgEF~W(Y7kDQ5Q&V+iQYpK&Gt#Gv@eXpvmqOu976Kh^0!3&@ijJ{wN_l{`u?6? zIuD(=Lq#1BA`yX`TfwUyMTMvMY?N`#ACsp#Ma7M25O?DJqVB#f$m;9Urip>nO_QeLQ? zT!gYE0!7M`ImcI0e9itpaMIT*Y4#M!gEf= z;!UEPWzeCZ#p?{S1NF%z$HKzIQuf0~8*=zx6^ zlPzH@%m%;yYNGUgjyQ>u6)NpQAI~~2UUv?>XSFzNtVZz2E^jdxoq0IcJ zTRBfG{ybxIH+rnJm4Vyd>_W|uA@@sbsfGG5iIhP_~;0GSF>rDxo{kgCa-HLcWQW6t&TZiM~^AaI8_O+4$PEzok$szqc}wc;St0 zSFivp63vpFb3uObNH~(#y;EKRtTYoPDm7{-dniL-=Kf!ET|L|{oO&U!#Q4A zt10A?{3~mJHld3ucMneP>MB7C3f?j80VE(HobBc|HAJ>Sa5Gc zhOn5Th)BQVWdi7%Q8W*$BI9~4g=<@J+}NlUHIEz%u}K!_7|i+6Rl|BV6^#^3X06ed zYJQk4ak}JQcR+!xQO=eZEA&*FdoRl)xfSCqsyy<=mFx>n&PNk`_i#nX_p={K+kgW@r z#w#z{QfOG4oCey})9%!j4$@^cO-Mf5l%QKnaUeTQ#}q4tj?Jr*>71-iHXS=( zOJp{=+cT4R<^Pz5q!Tt6 z&K*=M^}Mm3V!yJ?9!eGq3-S>kl!cjTE3uBRzAfKUm(`B**eIyY=*}U(>}WYR_ypXS zBSpSFm!HQW4l3OesJRlQr?)%#LoZ-u9$$=&(Z=?`pC(VXP)qZHI$f90YjucF`k?p68!&Oipq`7H(yAh%CP0uucT~zA z(;R7VeLt?}Dpi@CJ1Gm7j_wxUF3P9;o8IGOCd%Q4!d`(YgBptSo^837bim@f^oZH% ze5leq`ePy7-3FQ+4)OFFjL%ZRv_)P7a8Ax4#;krRyX@z+wJ$c+PKPWDwN6zI_1hIF z$cCcbksVS4mBPm=FS?Tl<5Cc2p@v~orP8hNtPRxSrO?r+cl!KU`K4tq?ic80G3P0K z3_=#YMOV)V=#uC2*M@=@63xb0g^5veH9(ziM!VyEwsP5S%2w`IJ=Km|D%Uh>Se<&s zChUfVv_3qr^Ozl5sOi@N-RKq+8C%^62?-trg&VqM(hs?7vFm9=ObAo{&P}7mu0w&6 z=}!Krl_}rwT`3=-Gh+y~##~l>#__EhgM;_Fym(kMqnmq3DHjVnCuCp7JoX#&BQj-o zP3yzxV<$1Xt#eo}8;UzY_85Vt?G@Mu6-*4q%2xs?Wiy~(?uyyQMEU4ZJT$!`bw8I% zz+6lh6TQ41@^Xj~W$!i^KKGZi7PS1(yFoVw5%~kTT}_ek25(`3G`Z_OR7b{Hpxq5q zUg&Jtu&><~Z+kXjpvZqd!l2!;bX!d34YXko7B%(f{IkUH-9d9HDC6-4yzKsZxVJ~>n~Swyp{!TExr4=$kf>?5pS#S+He61ixEbq6l#?Z{ zzMeHqPUj-)80aW&L+NPTU*bH<)>8Mc4PW10mgoH1>WMm1__e_+TKOp|6SO(t-&H`% z9DTlCrZ$RRM<@z)5V_Fh)vpXgqi0zU*~{uw6K#~O>}#=>e>1GKJ+4(8&;HigoD0fv zhsRzDn`AaYn>5P$ScE=XH?Xct6r|tpm}g8^AW8z8>7ID zK+~}_yc}d6tazTX69!rjg=9NWLa3c+C8X^AE4z?#rgdV9Px}c(ooi*}Q$o%k6kTRg zman5j9QLCgM5@DILVnR5VNoQ3M(a#rvE^vZdU)d~auKwd7;uSaj`L9Aw<($;^DEqV zcf?rdZFx?%OnC?{+zD|HPs-|H0(v>8)-2hJv5;L9yhpwGf_(zEr$_#!2-q71)|K6v zHtfR^jn=Yl#kcOj6{fg2GuTB2W+58i?0Atf_T6qU^oTv)bvME@c1@(<`?>zuI4(38M!; zC~<`#xrhtbiUV<|dQYv3%fzEI@sRIgPLM4%+yz`&^x(34x3itbPdeU0B2KffL1{cD z9&?)YEWO}UDvzrH+J9iPq9%51N6>pN>_#`1*(H!+$hO!_ z7#Tfj-2}16QkEzQJtpyC6fxta-knX@X0?<@Fh}$DjUftZoYuskWZQ&t7-qB1q+>HR zJ$I*bu_SUX*bVK_R%)P~4X|NAfm~b}lAc^N_E3)P^&?g;$rT}Eo25sSjOXOlhV*c) z^wxv6!N08Hp*#>8DfWAO}CP-He>=9Is5-&yT+ztX2L zet)xqydclUA4q9|c#b{S1fyqhvMVUh&3aiMj2=hD$uWGhkj};dN;#KU$%?>mYo)#c zJvde?#TdueIVNY?Rdm&~?BbIgkFk}YnHvIZD8H4-v7@6^0K3sjh=!c~k$!yg>Ed~1DJziMQ2Wj{bO-Ot!G5xCB!p3Rq2avJ?<8s_ccJ(2wPhYu1#7o#8O}~u z)C^fP*Ry;b(!yWnIYN2x`z#wbnM{-KnO7xe@+52MhXU#s|?Dg)aEJWTpa5@fWQ z;qw*UbgeBGo&w4FSi>h*AxVpRJ2p!tfh&~l7TBImvobi{(VOx^aQI1})7gfFb}E?8 zv?b6I>d`Qctf0|?g};D)Klqj+1VDFUhgJ<=FiPI#46Tp4^CXG`RiNLKTfPxkM+IgF z4sum}sXUC{!JclWuF*Mva~jFsCAy2o0rK)vfSP+WP=c`@nlrXHO%R&4F3XWZ=Ss9E zn2Q4^6M>5zE$6hFMRPx0>(&*hQFFX+WjoY9bK#%M>JTp*{XKpz33YCyhvq2seST%L zUMukgI?0W|Ub173juJX@%Ah_Ix;RwYUgL*?4SG9Hu4Kn|DHCDOZtdKSb>dg_P`(e# z>iZH~p)MRQ9wj>;J2O|(b;5gbdgW24=twVwICN~k7uqH$61vdo_R3E#>)o`hDU<

*iA<-^s@JrI(hIhUnD}u&L)@zODJJ|f%+y1ROVVx2VDxCOk}L8o@LF> z7bq_BLb=QO?*MC)v!ge=_Q+DuJuhEnOdIyv9)FYsWWgBa~d5OvU!ecP1BOrthuA&j6?;3i|{)!g~v~9+A z;L#@3InagX))99)WtBh~blY7?C>yGe%Iu)~71+6sbzFB)Q)BO!ZPLS_m~_zD*y!^7 zqid-4#T#4b*=}qIB{Qe!KAa!YbJ5ykA{e2V(6zs_zj!7obsdYb%YOlM|3G&(1Yowq z&ka(R*4`7nI5)uKAtO>wm1t!@iAAr176$R9eFYs1 zq7wLd&PCVPLGxpAX}M>5DL2r|U8ckWo?Y2^Ht5c>@=gcE$+TvgpFq43bU>y+~#;`}Uow(xiCE2XEH8dXB+e`TN<} z{c%wFZ7Fw2reT(JXdwaGc{V7D^17AUi7Qq#+nxk5=6MVyoERJpsatLgG+?Hi&I{T% zx(dgl7_9i#N(qjz0fD?Ct6bx2qAVnZDbOx5dOS3nAlDC2#~Lep?PizjGW2-Iw%ggT z%VJ*gZpr4d4%Sa7k=lnEs`DZVUEF1h%sE>gyB>v*3=qb};YU%Ce}FcGNIALIMA?ul z?_=p5sdlF1ugIvm)HSxO_xDw?ZstOuS zP;yYv%Z%w7&KkJ9rKbATYZHag)3OOp2pZ@?Mc1Snrnz_EkSoosyB70AWLqJ8{k){s zfHuC0iu%%m>y$kKcA^hnwk?Xzit}!Kmo=kAXRcPf-)^m*`?v%b3yosU!0&t31b>JRpL4wsr4pKrSSK zeE*>C3beBBL-P0SpnkD)qinWnAs30BZ6&%3t-AAM(MkpPW^0!nOvSfVTb8Y?+ zJXZ43iOt*cV;d~5r%FhuYDE@RXc$XO$th?-F{_8rEa{XB!fu;cEAu!r@J?k9zi%1Z z8&@;^dX(rKqL(c>`|rHvSX2I1t~VQAl|`n~SGg_HtcfM?-53TJN{*P;o;_GSi?*BJ zTG+;(B?9!ox1p@VIkLp{Dv}oL7EUke*!le>ra^b{pSGE zEmt?oMDpB3n!ott3e5-%LZ*)_M!f4{;@IWuH0)(^i#{*>1+*hg>n+stt&?*pzau32I9>SK?HDvAe`dX%*X1cA!I8sI8a=x^ zUmqeZ`%tNdc!-|G=$_<0QeeN|N>xiVQG18VEvKTsP^`!Ry*0~LMocM}c%S}m)5kbwNg(Ab@9O!j zr7U}LDb+#?h=Z%ocN;J*ezFu&bf-c=E(8Y8Z~L3!FTh*#)m>BCx@m7aH6`Ku)2N{qqZg{Lof%&0Ksb#Gac|rWi8U&{`fzmvlJ~&5Lt8eWTD>^2u)Rq$AVZ zpIe#=#Ovz|T_Ji#M+(gaZMQ(AO>s>&(E57@cji&)wRkeG;gkI}i%00;P*i|vp$27z zr-qYH(-=wmuu96}W3)u;Px3!Zx?ElIrE^$IGLh%2%(?M|ro1N6D9bj8Zl2f;iYC7) z4-p^ZGSxVp?C?VS(iUr}k%U`H(3XAp1>8c}@<3k4?f$+G<6`T0Og@H7B>Y}m_LfjM zk!Aj7XQd?FyM5Z^dInjS46B>6XJrlJWE<}Mm9@X1(0m%kTYxnxE3Et5caE+6={qQP zaiv#mS@!tt(L_f}d--|I?_UH_1xK(tM0e%!Rv4_OM&6a~S0_Yh%QQ^U1-)8(iM~Oz zsj^vuZfFzaD8Ya$T^ZF5XC4Xk79J!@YC;Vc{w`56`QF#o;|ZxKG}j1a0t1jnQwS&VwK{ zPn-By%f6LP%MPl80g0f(ft?`X8=ORUPSD7!K;fb}I8K8dr~;XC&{>V*L+FVPz?OZO zx%)(y@vD~K*M?##hLJN&Y5a0 z+Mq}zqd!&L_?`NSRHF?SZJ>f|=*29#(B;N;KsTG>J*kXBx^tw8O*Vm!wWo=f^MabC zAkc+Jn3!Ct_T(|9{9bOD0>NllFIP;erRiBgz0J%Nr@ft-C~Nl& zNR?GD!y9kOmi5j<4dUMb-C3`4QzlAtch0+b)jSI4R90S6847i_cAjEXSAXor^}?!kd5C1V>MhOJpB{6|eBGaj-_ zuBK-x?UenolZj-8dMDndv2Akw-v!67vU#%{?#AO)_ot=ub_wbg&3Mnzi9?vLZCcjF zCIIRX$d|+xQATdJpe?(m&0=uWESmD10=$q6#9Xm~PO2S$SnXC;yIRtZ z9K?&xi>2*!os_E&;@sLC(c)Z7qLSqdU(%gqW2H29t!<^~r|=RFf>IRLTp+$C9!cBI z<;XMEVQZXUH&K0T^*nKu^fchw0VYvRN@(nCL$lr)Kc}A?}-{F{ayJYZc6m#maT)&ijGXW z_N*j{`mXD{{f^y(W0x4X*^}Z&_Be=(FwtpYrau}eP^WYcG=oi|y}g3Q!r^>e=0iS@ zr0K_6h-U1!<6MOvWWc4QjnI^^`X6{wtMVBk9)l-uBKs7a`aBI z5S^pQ^EgW-;!{>xB#frJW1u;iq7z)8uq`(+Re&zM|MiebbSBa1ql^XZB_D=$(1wu` zMl<8u5rZoipmv3qgU(Ifo4P(cWCw@-cm@yVM`TPNSkI+rEw_8!IXipgn_Xj*CDUqx z$G04xMw&7v-BKdM^mjn9t~6n2WEOe{f$3E+el0lIXRCs!cYX}ro&v>bf|UD4>&}Ml zpWBC?lMEtJchXeWob%M;D><>t#-_rOAVwE=;>YJ*Uo584la5d?cZt!P?qqjYR_+|B zJXXk}%g%MP4cn|GNAzt!Z4pJ4NG%mhERU6Li3-T~fUVPTY`m!z8oRVKk=&d~d7Fd6 zWFmhk?E24*$@Oj$VL#_67MQ3bQXA={d$FNQy7h^0$Yl>1>-9A}sm4po2kJ0X=_3AhM4RspjoTcP8Ut|R92B~%y@}oU% zpx2F}J#!|V@+iQyQEQ9M!)Pri;m>jGo>-k~I`s?_EWM{pZZ!EhX4!NHSzIzon2vN& z@@!OoYpT`w8%#5tjHy1XW=nI7B+CnWD*{7J2b`(wcNlIru6825PCtTc4$y0hIMw2G4^0hVSa3kn6(4D#qy)ipzIzvd%1jYEHN96^)?WYH;S-~ZP zPwNtUKJ;btGOBQSqdg@$sZXcTG4l0x%%7(k@~ya*$I(G z82zbmMKx7_C{HJ{IEoSq>13NnRZ%=8pGYKFI!d?9MSJqeJtfQXaO$`{%z0DI5l4(~ zBw5_^Q7BgW4;8mo_TkuSIG<6EKHx0-O98weHMEz=Q@2xt>28Gf>~8Z5G-L^gmsKqt zR@TORl2wk2c;Dx`K>?~f&+I8_PCrx$bfAuu@7UJ@vFV)OQ`1XK+OK>B7pF5nFPGxU zkNGGSr6q1hV&VpswFQBYF<){kLKuq+Wf)EFZ>f>pH6_1HwRalxMaY8`-j(|Rbnxux zgbNEzrB_I66*PHy6;G69fA3W(g0Inxb5kBb-!m`6GTVJQ09WWj$IzFVsg$ItBa|u% z#4GYbAgNAdwVPaKIz_VDJg{!wJ(rwQItG7rCbzse7(gdk1)hs;i~B^C;u>`XFn0WoX{MwkDh~`>PsG&;yRAVqI1V!EOrl6R6gtvhWe@r$tqu}W`r*leFhxq`B{#a6AerMEHKYz#-Z3;ceSYO6K&=y7 z*q^8r@ldtO!t!OhogS^__O5g?z(^sGenLvv|OB#Cm$& z3=H~9X|OUZWdk1g_Emv@A5v3BbSRBG=>0{CAh;ml; z@T8CCP7a(a#d|iF75V1@iV9dn@foGrF!v}wDEm)|=cZ8!<_tN;%Khl5nM#-ZWtEKu zV&dZSSYTLn<%L=Dc!q8Y0^RHa{b+4{$$y5vN5GVRF`-@8}rGzfT& z?3J3F^zCQ||8KoEP?*0=<8&>90pfbDlwB*esU5#LEim-b(xwiU*iNmo`H0or#+yk- zcCY68dz(m6OxZTl_+f_d^Kss|=vn=@Liv&@q&4F|EC0pZrA3F?A}`LnGtfkNuh}*; zRq%2L;P-JK@?gUAQ8RRi9y^YE1(>*ma4YAGFnxyi|MAwmLXJTn7y zvHvaxt7O@oeW(zqb)CRDg!pfVxP~ocXLMqKYcd6e+oj8c>o?z|1*v%%K4z;**;CuU zSB%h`Ao+6?>GWG%q@JmL{HB&HH40A1QOsm8FTw@%keoy4$yh;Td#j%c`9I%Z?k?8iw zX;m*i&SH`$!{gFloHzLZrL-uW0i4=l!7vL zUr?+r%KKfYAY?RhJdrO|Xtq{@PL^$?o{5FUYK)7BR6TM)A6Y{tEW+bV6_8v_iEMIc zMy4hzFk_hHQNvCds z$Q-)SgKky6?9CrrgF#e6GJi~FL8Bp<`!0*+$6H6LNDFpiXQ5;A12~|$Zp8vjSq@xX zhWUH!$$GefN^R}NDn)Yzz4i@IPI0d7T-sq zdz3v)`njm?It~KAC8T^8g&N0=WyaA60|P z+`~ex)D)cr`Fv-3^1Zkl7hQvp5`~#snKm9#m(ffYt?UxMkQI!?QrhV_tY9R!RZOPC z;KB7#<7Scd$l$VQf)m@nybF`Eg{NWqwyFzuDU(l%uX$y`?Uc-*CG86O}emW-8vNQ!6YzQ*oPa*VqJI%IT1yof*Iz z!ln}dDh=u(uec)aT~=pomSr6*9A&1`Y#F;AE%^ZYzR`i=i!i?L9eOsKW@ZSq;(Ta5 zGTkBiaLVoXK}tUF>ez#dLvgDswcYd{)*-iz`IenxX854S(L4*U#Tb0Qp2l@5DJ2tN zhw1wHuSPyF$F!{)YGu*hZgz@wx8&Wq4 z(b#cfTNN&wa-Pk_VSoZpNzid6_etCI0G0|7`d>Cg@tZ0@IYb-u8q=xD=9d!LJrJ*| zL5Cb&rF8fGDymM8vaGJ4%WN2dHV7bPb?Ax0F3}%(y3wAQ8Aa#sre2~8bm=Cz$fwn{ z^$l+Pt>n8!W5q;7(P>Zhm^H`~rFC%wq~jq)pJK=oO+jlrAC7W+WEMF^gAAh`FRs3G z3Ptdmng6EG8KGzmm51^ZVufPt6+OiS9oW0NjC#~CaDA0g>5~h!9xE@cb?o9JG*dXn z?pgF=5eE=ggxPrg2+Qx@-OY@K)3)Z9zs9P0z0)e_BqmOe&CbskdTo(P4^O#t?{1dzf`DQZ z>LSbWXh|>UkG=EpL!}EtZ6I$eqrZ5adrdU*qV_PvE6~!-Rs0j^DwIusH)Rj-X!VNT zLJB80t7j<3ZJTBEd!7lb=I^A$ARGMHTFmnQN&{A5S~dZtG6g? zQBOA9T~5y$Zm#BKl8-piDh64lyJ6uiIkOv`m##@*O{L-Ry(hIhegyfDf9Oqg(1fTA zhMblARQualh|eAbCA6jm%`ecKDs9CxO@4lHOOqcEsCcZ>nSOEW$?lV$(%D-vuj45_ zEc9OSg{9+s_p?+(ys~VZte}eQ1GVe^3-A3Cr$ZY=zK-iuv!nBs!(B;z@F%i9=RMeb z#7TaPPAHB=`TDtVD+$w|_$;%_Tp7**5$co$^#@uNA@t|;FicM_q3UGZSaFz~G8|!a zQRJZ4t>}7)KX}rE(fvcG-Aoj0{Kse4ESSpC_JkjLc3A*ekqI6a`NyYKIs(YLXE_WQ@&P8`+JfLhh!=6B_&s|x147Q}Vi`D8q z{X)f(3wZg&{ldzOik>Q~g+yOrOWCfGtl>&_<35DM?t06%r?ir_Ky_9wc@3|Q4U$zT z^&9BKV@FD3=5n1(;2?Fh(apA&#}{jNAAF39GzV_3qKjR@kWUXNkFqk@QHqaDtd;!z z&I~Cl*R!0LKd{=pSC_{igO3h%|6a1x`p_*NccZ6~`AIarU#ngim}?*ro^p5f%+Yjg z@4pJP{uGo#t>qBfNXIm!)-<|*Nh|d__)y79E-U4T%f{X!-B~}5M7z=#3^z>T={E>{ zIM_qhW?sNACYx%z3BhDQK4kj|dFY$*cp41Sno2x$Q3W@5qt*VHAx)LctJuv@tejgj zvvL)l8FJ?OB`f2CP=a~Vg=wbUgt+$h0_CXa7i*u?QpdYYDZrRBdFHv^J2u&;UeLYK zkV`z|;$;3hF&;O+(aNLbITuY(Yu7YUIca(fRnW{RrKeY-GwaQ_Y=~1OlTL>GbwM-1 zo04D*qto6A|13*@wNSg<2Kq}Gska1W*Q)t78)}r`jAVrp<@0Oj*NeOol>vdwYm#XB zn(|c`?T$UT@=(k4of;COb+Gp2eC~S11DJE+1v%=ln}#j4hc%3hok5x0q( zC1> z1q#?330Es=*_B7`W^<>_r1ypJ|4<8UVxpVBPEf(_<=0wYCVCZ+Y)?I-TJf$J-Q!U9 zY*LlhgZw_;W=@njkJlBW(BO7euJ)7LuUX2~mCSM$Q-+tQVi;?p^cOSSw|^77?46e9 z&FBar>xmOodwZTCaTs_l#`H6|dYHKEXfx9rVuBJ2zT+^3S`n_?eVIr)Q1*iuh&Ub< z|6p|YJPE6V%!MANT`3kRD?K0R%&*V_M_CwK%X*z^ z;XbeP9IwHyR$9AkZfBJ%s+dufKx*{lg%lkw)Bs7;yTTXXp4s2a`jC@BHsSCwIdCLu zPYt0xD<~`)k+g%x)laqJUI(o#Tt$wgWNGQLtmp*ipQnIgxW!QGkL zy-zflnvfhBCCM?0_nQ30DhJ*vndY`{r;=7K@O-z*_h0ww|fc zTV6aDtC!HrmeQCZW%k0MeP?B-D}_S^U93^raQWIQJL@eJU6=fmqFJTdy|Q6-a=PA= z$zOHrF3^(#w3EFkZ>hZl<$U8TtwQrwj}_!5C}@uT8Z7GpjWEXd5^vDBa`er3$gI;x zzFErqN>9l*D;yrJFW+o4-1GbnR@h3L(|f>*wAwPvPRV4`360&H&)x{j>hPKTJVg(Ij=%pX;y6;2UTP;K{rS8 zRWd9+RQCA$RhE1!0=~a2j?4-!l_Zo(I8T_{&M|zbVDLy{dLfl}IYGIUyHc*0#U7m0 zqH;Yy^r*pWfWk&`?iF;zBcgB)t7I%6sEo{53}TD6AF=N_#bqetQE)Hw1*)yo#El$g?a zJAXT14Q1QvpyQ=&DyQnVHlW=g>XdJL=|hZW-`AA|RK0M1YR}D2H7sBKXsU%7WOp4o zgVpHhMOOL5@XlH(w9X?qH()wAe@M3tnx98{k@uYP*L+5*3D9rZZ11h}d>u3->-f>D zmY)jI-qWCg77DYg${kJGQpf8tY0fQuy*~RGl2Y^^9={ZOI$iuwp9p0|1MmNXuCyIE ztb3+QjHMrk}oYl`D^mwmTwXObUKH%86ugCKc9HpBd>pR_ab`O_{_ZNL*eQ!2K0c zey{+EMb_?%XHdrUpdt8wl;{Ims6M9*1uvsaB5At$lycz(UFo1hEG0|ZVhAdhoFDtp z%4Z+B%N5DU#7gJu%t>m*tq$eAFc%e>9_3Ga$@R`*zLn^SuHt_5py(E(VNh2!S$4?Z zBGbi0Gh4a(NSaY1!dK4_Wa>6J{(Ljy5&BKs_9{%ABK-!ivYQI`=s9TI7ro*u6=6KX z4ISS>v9?ZCRN)>J&2MG%cWw;b8A!3cnD8ECNF3DVn78uszayMGbBiA&qGbTWL!eWM z)~rnIND)6RZw@dAc$jHTg@xaU@y?S*hCyZ~lFVu}s+OW!C*X)v7CkHW7sY-?CsTC-(2mcmyu<+>l%qqX1Yyu;JeM2X=!OC75lsxg zwR1tJ!4E7C*_~?YyJUz?IlVqKeF;~4lvM25mQXaQEGsLcY*)T+j5?}?#th10TB?SU zB`}qs0`}fdU1oPEiY}V<+d*YrVO*}&Ao8P^YVaYt(OD#{_IdMq8@1)p01=YjxD*}H zqrtkyk&rMOs6H>NC@LP>R+K#@Mzb_;eK=!H6)=q!?r9bA%VVT$CLu3Hp$Dg7i&y?l z+eBrYrzS~-8ZRHBHL?PqTTZ~}%yGDN9gHptcPMeF(ZgO`T;iW{4^n>_!*&syeUs3# z#dx?VDmD+e?fjX8CgMarSU*!LH)28IU><|k|IaMkR%I6RRKxZ#KI&YRCf}f&L9jjWKdkb=sF zKsN6WTQ8J~50;uCYQ2^fKr*lSt>K&n^xPAyz;yWd{wgo3i~|Bcp3-0Dr9Z+sW|1F_ za5349a?UD_Y-wS<>dx!D(C_>qz0fqPVIua04ZMUxXKfF+c|_S>d&r zD$mX%w(u-b74-fIXUm{##-Oe4*`WE3BzFs)xy^e0x@C(iPbE$Bijq;VX0*Pfi&-Z$ zFQ^-Jf%17(jgMaN`vhv9Wud^YS2h(i{>qxMv7AzWS-aCI%tDo=Ls?CCYR&w<)T1fVjL2dKR z3dMMy8PkcB5pQ z-y)!~UAogOty7L3cXbTdF_mN5n&r6lkC8sXDQCq?fR!#QZF`M$M%D13d-GD+N4 z3EXdkJWTEAL7WH00~b(q!L{*RC@lans;nGuThZV7w|0!s%1lqV7}b!K-2*Ggb3JrF zw0)M^-8c=6G&2=jp0s@7^iew5hf?VAxdIK8RF~(b6@92f2OqqC%AbUNQZ|f?AWSaj zKl^0$V5CZsT<5IUS3F5YG=iqcP|4v{VyHV%vOgbZkzsJd_SiMzZZukmQ&u7@d^k06 z$~SSu{VMH}eg_$vist61i@Sri6E~Q?Bw>pEnH=>x!*|qQq(Yr3(Oz?*p20v|BhoK+ zjVfjuU2k^&=9|6abs$0n8rCEA2vW$uq@ArPrZHc?hmkEitP+(IHw zc^bio8aR>vPj8&Q`h<7xF$2*Rm?02(fl|5P9a*jtKA$5|NT|BBkZ_8W8Hqnr^QMs} zp=%(_#FN;mA5pl`J(*VdG7rv9oZLi650!p->9!`4UnN)>K?$jRl`VlQa)P#_;sh-O zmLL6)5B7d#S}D|^3q4HCjY_pb-_S`9V%w)8v4bx5jELL_BA?ly?~`(<7&R}=hN1oJ zC`E6m`*x(nBRB33m1mU>&c}n58)<({w8q+}VYjc^#VXt)f7tW}c1|6&q)3qdy02pv z^k~yTJEdO698t$rb=|1K3xbANrC>YIaU@e@JW++m$Gf=(y$*z;u4l|Da`z^!rDG*f z2QXx^3*@vTlR#X;(a>ioMaTZ$L=XiiJt$9)^r`}u(YjOrN%$-rZFn!v4iD2vp<>8E2ibmN z6F1h#?WrQx@K#vyNY_uMi1`(|6@`(}HnO(XBzcN34hoe^nD9gA<|$my7RS^wsVG@h zzX>bFgfl|Xc1%nKN{yc7itVB$Ew;z`J1+kfXlKc<;$(DijfgTNv?iZT@@xPIb;O&B zu?AiwrI~WjMBC(<4fxqFnpD?G)Uwj0zO-1P0B#Z$DYS3LD5?Adf1g;EM{*92)|I_9 z3LH%5{>1}76zxGCF>>`>BAuLPu}bh$52{k#Ns2w<2Q5M6v0rRv8@*k6OssB;v#zM~ z{-Lldykz7H#dC0WAnB{>m&9|0IH!kc2_QQp+fg;5-9w4I>S51s3XP4n_lyrS{C^_mwu&Xv6 zV>nwBxgd=&Y$GvdUve&^$_zC+$slc20kxp%V8>N*cIFC-W+sBFFo-<)?ve~JfE1jt zs(UlxASs)BZtN0t#Id4reIzE&cPhPkTThb0xNKoYlO7|~Ya^F&0pzY$p|qgU7UbUY zYexm&JH#GRxk0ISqjGRNJv5kD#Mn907UmhbKpF0XWAwAXC`$JIIxtp0GxAF+Z)jmUV^6*3j!?muiygTGq;qM0ztG1rOF+0N7 zovQLIGguht#gX&G5FVZ$Rj~F3T~CIkf+jJLhe1dovfPMqo3%A&ZrVZ>FU5w$oF0z* z8+*&GUa9w}h!M1|b5~pFis=VoegHv0zQ2nmfX1yVGI`MQV#{9mb`EIX5~&3}gfz9Y zl~%$c6d?1)qTk^PQdkK+Y@B3GUPqLshawa0s+OSL7t(n=)@VGTK-t^$@@b}LJ>$sD>jCk0`B4gk5{2UCDO%#K)j*tF&wW2z9e(PLAf0T8D*-J+>Te^#eI2z(Y1L(KbxKX_( zLGuWD978*Aq#}0cCo_CXIE#fPjOVy$f`)QURIz(SYNZ(%o3g1}V%rKj!w&?gh_L>w#f^^NrrH?UAVC^A5` zY>*j4G{q&fK|T{I6mwk(#MsRrxU0HyYGWejjj4=){#o8bL^kr_+qaT>ZO~SBK2Xv$ zjvCBkP;EJ9t-*#bl^w5!QrL1*OH95;V$oJ<)1YN%&G?u{O|oTGKgMTk>y16aQ%dc&!P8`&9`a(y34AoiXpBBS~&S{QN>|d9}3!1G+xXHZXV!0d`s}5P# zyK?qMd4i5OaBqDYqa8)#XwE%@?dZdEG>ZS+%ANGvUj(TV>lnl6*~>}Ck_j{8ttI`= zmTy+^@=N7s5nI7AE5?H`1{Rh^fs8#U!cmv+n{FAHa{CxUi-Gs-{F#0ipX{u|lOs() zuvkN~3=`FPuICeCbF&r1KuW*+;n&um8_7t8u}n7sM*5X_B<*`fr5a?}%nirRn(rUs z!GeosFDO((2W?r~MbhFycQ$C$50w%xU$^dH|C!Z`n?Rg&-v~I6@}A~6Z5*(wd6Hw= zIStBG%SA9&&=S4qJJL6@>suQJwupMWn!>N`Qnbdmx)!%e>{+>~Q4iO-8T%(dDrkLY z)*M5fN@0wk!#hj?GSB+v)eLM~Zqcq>fkEhqYEfJu^`Q6^ zr5>@oDi0Er9Cop`&S*E*CdDh+qqpIz6o>Zue#;lvQcXnA4%3UC@=$%A5n9Jwst&-+ zL?UPOEC@5xVK!QJG|z>vQof#?%X4yR+we~GR9aUMRTLIULdKvB8KcdW+_q}9dJ?J* z>}4$Ra32(2H)RU4y648raAQC3g9qNa#BqRx8i;Miy^e$lodFP(q>PTr2pa6{=N^TF zZYi?X3wE@M!jJvwx5JpDxC*w|i0|kuIalgPg5gr>B(L}+5qRA=V1p$`^_+UNR2hER zj5tS*?9A6ak09HE#9ztMzH;aN(3Q85h=tLNmcoC6qD6_2H2o5l)Nl1e29{H@#z+rq zq~D1tS11>-c%3;AD+YwMP{nji_`IT`otXuDR5^}}0s007d6#q$0joSthHysUZe^oj z2qO>pJ@eJyPBFTpx|AIChG0?hV5J97L3QG+)gSET&#@F90R(TXg6;E54+RL78IWIk z_>hYIW8a=PeSUwH2>1nI`cQHoB6p%Jh}A6uUKDXgp+~+#dx5H+Fk+vSgz=K2qkGmC z8L8Q-~j;59ru_Eu4^p=RHZGtibt1;h;(MX6W7%>>QKk)@Cs zpQi}F_E3~Wh3?SQWpVX6ZMhDoLa+HXy|aQMaoZ$CSv?>Wltkw7Dm;Q2??RZ1Tuq>h z+yByn^J`*&H9FfLl=t1h7kq~R+A%Sm;jWs8XPHcRMA(eBGl|jIrCO4-9dFuFb#}CH z32NqpBM}FiW76Flwb|YqRTLxhdmiYDg;5eiiyRTwx|#CFlDxA`j=G-ka(7%tayqZI16sNaoJIyu%i(a7l8agO%YQ6$Vem7h{D0T%U3S={)rC%p(EG8L2 z)cjjhhNdfK6p?xcEzDsiMouy3F?SGFsi5W>8Pqz9@I9$7gZU4A-@<4Wndu(h-C6hnFH-0tt4!F_;lz@HER%$T?N0AZSgNfI!kF6N{6@lPx&ux&eo=RVe6DMx| zh&Fl*dmt8F~1`lp4n3~qeLwh$E3RyaB>9t8RSn3=LjKtH>8 z;$Elnvf96>f|o!kp*(xYgm1|m@6nqIZS-vH7nE`edJA#>zf5*nU)j0JoMgD?b{(KA z$peEV@TvHok$#ajH?Ry8#XuzfaaTlb69@IojAzD6#k74(i!g>g{<>G(uVg?%$@7Vo zBJl;}Tb-MiE7@qrkvhdrjf6Q08E7+A-vFVt6`dPr4%BL<0O>(h>hOz z&Dpr`F5g#J=mqbNd|t`NDbKcEq2fn$pzFvZIPb@nWkK4A05s^+XxbCnP@qn{{1>eD z(M!ApH(8c-rMB#h=lk8f$WOB*HN2_4bx>q$L9<(zb%{PFU{4xKQmXWx%AZ$a5|rA8 zlTUGZuhE{FR>Yz#M_-lz>g~}BYZB=i*+Ak={@*A8z_Jr(AlHIXL`D_Si?P+&Fm*sQp^DR)fi}_)-HCis;UXIeWX4Et zpXg9PCyzbFqxdr_zbxK${IBZ^^1pF~6O<@&UE43eR(li&(-q6Uk{6Y@_dsU?hgysW z=M|jf&Hhj`YO9n`+}M?-JMj}o=>vTg-fnbYrBotu+D^W%mWI{s8*lG^^;APwdCzGq>h?QD5|VY=LZf}aM_s#`I}(M*XIRcSlJwHfhpaQwhh0k zn6zWFO3fz`{c-wND4i|^H1@#9Eb&uB$*h>j*8SN(g0iXo${70Bwwgg-R>-*XFCVGV zo%py*7x_caCXA_cqj8F+Fn-aEA4>#(xYC}N!!9a-_w!UTvaX7;MteTI0!D0Nmph3u zgyG{z6PzuVWDwuO3#*gRg^@pHl~pMT6Z@NBc=g+HJ{0uk%N&JYrP-b)f~Sf1qH`OI zi79TCBfve0%gSSPQ+rD6IW-PJNc_b7(s9wfBsPdP#cK1>Cb4^o$YjRrMOm&JB25ni zhpj@1aqTFe7j(Dy*fxTDU#_MfTC?WLt8`OU67Vu8e9J#p3k4!2EC(GF>VQb3FyW&B zcok+c70+ZRqn)Ygr_#Z6a8v-ch)5#5Vyp1bSjv?492BEqa6@9Xm)v|mwB;cv1qZ)K z`}U14ss)OFOKy){(w6hCvO=4F3E5VdP*!YFI%%tV59p*U`_!hp5UMpl6LVv{670Z_Zpd*spb{qfOTdmaN7NzjWx42HYTmh zOZjHSCT(L168ihXOB`R{jDKfUZ>^c(Hal0=?;#xhgIM!UW z#y$Hy=u4#E{Sxj$@6YsL!{~k4)$gk@h9jKXhdy2gITo{I!&4iDm>R9_^qP+ZTvXD= zCecNZVRHggKt9`EW@Tq3ip5k3c(` zaXB1yT?z*#yZE>(8mYkI-xpI(WJP6R6HrF2W@NQTC0_em0bbqcePCq3trzP zx}M*7RXmhm>(n>VyOG1t3F1)fDLH*gk@?x2HqYv~PIV&ALaBWQ!1aw(Jmv}1@mXt!WfU0_bxfbd@^3b0V-dmd|k`DwQ5l3Dzr zsd9mhjzp5ltmz*U(*-C6U6$&AITKAN3)Wt$M<{F29Vk;U-CAQh=xSM8u?YQ8Z2))M zx0z0su5_fZh|)Z@(J-y7fnRco5G)fWeQ3QZk;<>B)Z-6bu-D_ovv_84=w^0-Y@t6M&NqHYw-PSc38BGepx`jH8raa(59a(+Q zNkZUKIlJGu&Py3PrHX1ZF0rzD4KC@0(Si1@M8;-t;(cFw>e6)W59J~`ho6_wx>ir4 zg$$Q92?RN4hkte5M~~4jigHnQOtEqaN)mmhm!ZLxC}z0ofwhXYR`$S6)119ET;5m& zu>*0Z>J$akqgvac%EF#>x*dFYe~M40rzd}3a`ZM#iJAe+rFuRr*T#OU`r+0yN}U

+m$5&k-Sp!p3Uo)DYQzW&^QjcJ*W*S>lN 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%0AF8bRZs#lUfFko4);mhgWci225Z1N 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�f0AM6ZF=sXwe9oXAQCwhkp);~iVaXA{3j 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 Q^Pm6wFE>+?oQN+F0AA(rod5s; literal 0 HcmV?d00001 From e87f05d8b74aca46ba5e60ca6a22a65aadf79510 Mon Sep 17 00:00:00 2001 From: Ignacio Vera Date: Wed, 4 Aug 2021 09:35:35 +0200 Subject: [PATCH 8/9] mute testFetchVectorTile (#76060) --- .../index/mapper/GeoShapeWithDocValuesFieldTypeTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldTypeTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldTypeTests.java index 30d6bbb6ab990..6e22e785e9691 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldTypeTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldTypeTests.java @@ -78,6 +78,7 @@ public void testFetchSourceValue() throws IOException { assertEquals(List.of(wktLineString, wktPoint), fetchSourceValue(mapper, sourceValue, "wkt")); } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/76059") public void testFetchVectorTile() throws IOException { fetchVectorTile(GeometryTestUtils.randomPoint()); fetchVectorTile(GeometryTestUtils.randomMultiPoint(false)); From 2d7d3bda90d6e833c6595d2673db23845d2eeb69 Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Wed, 4 Aug 2021 09:39:37 +0200 Subject: [PATCH 9/9] Use TestArtifact plugin instead of custom test jar tasks (#75931) Make use of test artifact plugin and avoid using custom test jars created manually. Co-authored-by: Elastic Machine --- x-pack/plugin/sql/qa/jdbc/security/build.gradle | 17 ++++------------- .../plugin/sql/qa/server/security/build.gradle | 17 ++++------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/x-pack/plugin/sql/qa/jdbc/security/build.gradle b/x-pack/plugin/sql/qa/jdbc/security/build.gradle index 950b5b2250641..917d319888cf1 100644 --- a/x-pack/plugin/sql/qa/jdbc/security/build.gradle +++ b/x-pack/plugin/sql/qa/jdbc/security/build.gradle @@ -1,29 +1,20 @@ import org.elasticsearch.gradle.internal.test.RestIntegTestTask +apply plugin: 'elasticsearch.internal-test-artifact' + dependencies { testImplementation project(':x-pack:plugin:core') } Project mainProject = project -configurations.create('testArtifacts') - -TaskProvider testJar = tasks.register("testJar", Jar) { - appendix 'test' - from sourceSets.test.output -} - -artifacts { - testArtifacts testJar -} - subprojects { // Use tests from the root security qa project in subprojects - configurations.create('testArtifacts') + configurations.create('testArtifacts').transitive(false) dependencies { testImplementation project(":x-pack:plugin:core") - testArtifacts project(path: mainProject.path, configuration: 'testArtifacts') + testArtifacts testArtifact(project(mainProject.path)) } testClusters.all { diff --git a/x-pack/plugin/sql/qa/server/security/build.gradle b/x-pack/plugin/sql/qa/server/security/build.gradle index 375c654645955..59bbba8b7e70d 100644 --- a/x-pack/plugin/sql/qa/server/security/build.gradle +++ b/x-pack/plugin/sql/qa/server/security/build.gradle @@ -1,30 +1,21 @@ +apply plugin: 'elasticsearch.internal-test-artifact' + dependencies { testImplementation project(':x-pack:plugin:core') } Project mainProject = project -configurations.create('testArtifacts') - -TaskProvider testJar = tasks.register("testJar", Jar) { - appendix 'test' - from sourceSets.test.output -} - -artifacts { - testArtifacts testJar -} - // Tests are pushed down to subprojects and will be checked there. tasks.named("testingConventions").configure { enabled = false } subprojects { // Use tests from the root security qa project in subprojects - configurations.create('testArtifacts') + configurations.create('testArtifacts').transitive(false) dependencies { testImplementation project(":x-pack:plugin:core") - testArtifacts project(path: mainProject.path, configuration: 'testArtifacts') + testArtifacts testArtifact(project(mainProject.path)) } testClusters.matching { it.name == "integTest" }.configureEach {

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