From 5c8c266a493c2eda8d21bc11edcd638a3e34ef8e Mon Sep 17 00:00:00 2001 From: samik Date: Mon, 11 Sep 2023 17:22:13 +0530 Subject: [PATCH] CDAP-20803-create-lro-db-proto-schema --- .../handlers/OperationRunHttpHandler.java | 106 ++++++++++ .../app/store/OperationRunDetail.java | 171 ++++++++++++++++ .../io/cdap/cdap/store/StoreDefinition.java | 43 ++++ .../proto/operationrun/OperationError.java | 61 ++++++ .../proto/operationrun/OperationMeta.java | 70 +++++++ .../proto/operationrun/OperationResource.java | 53 +++++ .../OperationResourceScopedError.java | 60 ++++++ .../cdap/proto/operationrun/OperationRun.java | 192 ++++++++++++++++++ .../operationrun/OperationRunStatus.java | 98 +++++++++ 9 files changed, 854 insertions(+) create mode 100644 cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/OperationRunHttpHandler.java create mode 100644 cdap-common/src/main/java/io/cdap/cdap/internal/app/store/OperationRunDetail.java create mode 100644 cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationError.java create mode 100644 cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationMeta.java create mode 100644 cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResource.java create mode 100644 cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResourceScopedError.java create mode 100644 cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRun.java create mode 100644 cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRunStatus.java diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/OperationRunHttpHandler.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/OperationRunHttpHandler.java new file mode 100644 index 000000000000..cb5ba40f7cc5 --- /dev/null +++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/OperationRunHttpHandler.java @@ -0,0 +1,106 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.gateway.handlers; + +import com.google.gson.Gson; +import com.google.inject.Inject; +import io.cdap.cdap.common.conf.Constants; +import io.cdap.cdap.common.security.AuditDetail; +import io.cdap.cdap.common.security.AuditPolicy; +import io.cdap.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler; +import io.cdap.cdap.proto.operationrun.OperationRun; +import io.cdap.http.HttpHandler; +import io.cdap.http.HttpResponder; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import java.util.ArrayList; +import java.util.List; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; + +/** + * The {@link HttpHandler} for handling REST calls to namespace endpoints. + */ +@Path(Constants.Gateway.API_VERSION_3 + "/namespaces/{namespace-id}/operations") +public class OperationRunHttpHandler extends AbstractAppFabricHttpHandler { + + private static final Gson GSON = new Gson(); + + @Inject + OperationRunHttpHandler() { + } + + /** + * API to fetch all running operations in a namespace. + * + * @param namespaceId Namespace to fetch runs from + * @param pageToken the token identifier for the current page requested in a paginated + * request + * @param pageSize the number of application details returned in a paginated request + * @param filter optional filters in EBNF grammar. Currently Only one status and one type + * filter is supported with AND expression. + */ + @GET + @Path("/") + public void scanOperations(HttpRequest request, HttpResponder responder, + @PathParam("namespace-id") String namespaceId, + @QueryParam("pageToken") String pageToken, + @QueryParam("pageSize") Integer pageSize, + @QueryParam("filter") String filter) { + // TODO(samik, CDAP-20812) fetch the operation runs from store + List runs = new ArrayList<>(); + responder.sendJson(HttpResponseStatus.OK, GSON.toJson(runs)); + } + + /** + * API to fetch operation run by id. + * + * @param namespaceId Namespace to fetch runs from + * @param runId id of the operation run + */ + @GET + @Path("/{id}") + public void getOperationRun(HttpRequest request, HttpResponder responder, + @PathParam("namespace-id") String namespaceId, + @PathParam("id") String runId) { + // // TODO(samik, CDAP-20813) fetch the operation runs from store + OperationRun run = null; + responder.sendJson(HttpResponseStatus.OK, GSON.toJson(run)); + } + + /** + * API to stop operation run by id. + * + * @param namespaceId Namespace to fetch runs from + * @param runId id of the operation run + */ + @POST + @Path("/{id}/stop") + @AuditPolicy(AuditDetail.REQUEST_BODY) + public void failOperation(FullHttpRequest request, HttpResponder responder, + @PathParam("namespace-id") String namespaceId, + @PathParam("id") String runId) { + // // TODO(samik, CDAP-20814) send the message to stop the operation + responder.sendString(HttpResponseStatus.OK, + String.format("Updated status for operation run %s in namespace '%s'.", runId, + namespaceId)); + } +} diff --git a/cdap-common/src/main/java/io/cdap/cdap/internal/app/store/OperationRunDetail.java b/cdap-common/src/main/java/io/cdap/cdap/internal/app/store/OperationRunDetail.java new file mode 100644 index 000000000000..7aefc37ef062 --- /dev/null +++ b/cdap-common/src/main/java/io/cdap/cdap/internal/app/store/OperationRunDetail.java @@ -0,0 +1,171 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.internal.app.store; + +import com.google.common.base.Objects; +import com.google.gson.annotations.SerializedName; +import io.cdap.cdap.proto.operationrun.OperationRun; +import java.util.Arrays; +import javax.annotation.Nullable; + +/** + * Store the meta information about operation runs in CDAP. This class contains all information the + * system needs about a run, which includes information that should not be exposed to users. + * {@link io.cdap.cdap.proto.operationrun.OperationRun} contains fields that are exposed to users, + * so everything else like the user request and principal goes here + * + * @param The type of the operation request + */ +public class OperationRunDetail { + + @SerializedName("run") + private final OperationRun run; + + // sourceid refers to the tms message id which has updated this run details + @SerializedName("sourceid") + @Nullable + private final byte[] sourceId; + + @SerializedName("principal") + @Nullable + private final String principal; + + @SerializedName("request") + private final T request; + + protected OperationRunDetail(OperationRun run, byte[] sourceId, @Nullable String principal, + T request) { + this.run = run; + this.sourceId = sourceId; + this.principal = principal; + this.request = request; + } + + @Nullable + public byte[] getSourceId() { + return sourceId; + } + + @Nullable + public String getPrincipal() { + return principal; + } + + public T getRequest() { + return request; + } + + public OperationRun getRun() { + return run; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OperationRunDetail that = (OperationRunDetail) o; + return Objects.equal(this.getRun(), that.getRun()) + && Arrays.equals(this.getSourceId(), that.getSourceId()) + && Objects.equal(this.getRequest(), that.getRequest()) + && Objects.equal(this.getPrincipal(), that.getPrincipal()); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), Arrays.hashCode(getSourceId()), getRequest(), + getPrincipal()); + } + + /** + * Builder to create a OperationRunDetail. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Builder to create OperationRunDetail. + * + * @param detail existing detail to copy fields from. + */ + public static Builder builder(OperationRunDetail detail) { + return new Builder<>(detail); + } + + /** + * Builds RunRecordMetas. + */ + public static class Builder { + + protected OperationRun run; + protected byte[] sourceId; + protected String principal; + protected T request; + + protected Builder() { + } + + protected Builder(OperationRunDetail detail) { + sourceId = detail.getSourceId(); + principal = detail.getPrincipal(); + request = detail.getRequest(); + run = detail.getRun(); + } + + public Builder setSourceId(byte[] sourceId) { + this.sourceId = sourceId; + return this; + } + + public Builder setPrincipal(String principal) { + this.principal = principal; + return this; + } + + public Builder setRequest(T request) { + this.request = request; + return this; + } + + public Builder setRun(OperationRun run) { + this.run = run; + return this; + } + + /** + * Validates input and returns a OperationRunDetail. + */ + public OperationRunDetail build() { + if (request == null) { + throw new IllegalArgumentException("Operation run request must be specified."); + } + if (sourceId == null) { + throw new IllegalArgumentException("Operation run source id must be specified."); + } + if (run == null) { + throw new IllegalArgumentException("Operation run must be specified."); + } + + return new OperationRunDetail<>(run, sourceId, principal, request); + } + } +} diff --git a/cdap-data-fabric/src/main/java/io/cdap/cdap/store/StoreDefinition.java b/cdap-data-fabric/src/main/java/io/cdap/cdap/store/StoreDefinition.java index 3d917886fa2f..0c71048b439f 100644 --- a/cdap-data-fabric/src/main/java/io/cdap/cdap/store/StoreDefinition.java +++ b/cdap-data-fabric/src/main/java/io/cdap/cdap/store/StoreDefinition.java @@ -71,6 +71,7 @@ public static void createAllTables(StructuredTableAdmin tableAdmin) throws IOExc TetheringStore.create(tableAdmin); AppStateStore.create(tableAdmin); CredentialProviderStore.create(tableAdmin); + OperationRunsStore.create(tableAdmin); } /** @@ -1323,4 +1324,46 @@ public static void create(StructuredTableAdmin tableAdmin) throws IOException { createIfNotExists(tableAdmin, IDENTITY_TABLE_SPEC); } } + + /** + * Schemas for operation runs. + */ + public static final class OperationRunsStore { + public static final StructuredTableId OPERATION_RUNS = + new StructuredTableId("operation_runs"); + + public static final String ID_FIELD = "id"; + public static final String NAMESPACE_FIELD = "namespace"; + public static final String TYPE_FIELD = "type"; + public static final String STATUS_FIELD = "status"; + public static final String START_TIME_FIELD = "start_time"; + public static final String UPDATE_TIME_FIELD = "update_time"; + // contains serialized OperationRunDetail + public static final String DETAILS_FIELD = "details"; + public static final StructuredTableSpecification OPERATION_RUNS_TABLE_SPEC = + new StructuredTableSpecification.Builder() + .withId(OPERATION_RUNS) + .withFields( + Fields.stringType(ID_FIELD), + Fields.stringType(NAMESPACE_FIELD), + Fields.stringType(TYPE_FIELD), + Fields.stringType(STATUS_FIELD), + Fields.longType(START_TIME_FIELD), + Fields.longType(UPDATE_TIME_FIELD), + Fields.stringType(DETAILS_FIELD) + ) + .withPrimaryKeys(NAMESPACE_FIELD, ID_FIELD) + .withIndexes(TYPE_FIELD, STATUS_FIELD, START_TIME_FIELD) + .build(); + + /** + * Creates operation store tables. + * + * @param tableAdmin The table admin to use. + * @throws IOException If table creation fails. + */ + public static void create(StructuredTableAdmin tableAdmin) throws IOException { + createIfNotExists(tableAdmin, OPERATION_RUNS_TABLE_SPEC); + } + } } diff --git a/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationError.java b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationError.java new file mode 100644 index 000000000000..f9c258f27258 --- /dev/null +++ b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationError.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.proto.operationrun; + +import java.util.Collection; +import java.util.Objects; + +/** + * Error representation for Operation Run. + */ +public class OperationError { + + private final String message; + private final Collection details; + + public OperationError(String message, Collection details) { + this.message = message; + this.details = details; + } + + public String getMessage() { + return message; + } + + public Collection getDetails() { + return details; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OperationError that = (OperationError) o; + + return this.details.equals(that.details); + } + + @Override + public int hashCode() { + return Objects.hash(details); + } +} diff --git a/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationMeta.java b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationMeta.java new file mode 100644 index 000000000000..9d1cb31793e3 --- /dev/null +++ b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationMeta.java @@ -0,0 +1,70 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.proto.operationrun; + +import java.time.Instant; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; + +/** + * Metadata for an operation includes + * 1. The resources on which operation is executed + * 2. Timestamp of operation create + * 3. Timestamp of operation endtime + */ +public class OperationMeta { + private final Set resources; + private final Instant createTime; + + @Nullable + private final Instant endTime; + + /** + * Default constructor for OperationMeta. + * + * @param resources list of resources the operation is executed + * @param createTime timestamp when the operation was created + * @param endTime timestamp when the operation reached an end state + */ + public OperationMeta(Set resources, Instant createTime, @Nullable Instant endTime) { + this.resources = resources; + this.createTime = createTime; + this.endTime = endTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OperationMeta that = (OperationMeta) o; + + return this.resources.equals(that.resources) + && Objects.equals(this.createTime, that.createTime) + && Objects.equals(this.endTime, that.endTime); + } + + @Override + public int hashCode() { + return Objects.hash(resources, createTime, endTime); + } +} diff --git a/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResource.java b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResource.java new file mode 100644 index 000000000000..9572a3af869f --- /dev/null +++ b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResource.java @@ -0,0 +1,53 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.proto.operationrun; + +import java.util.Objects; + +/** + * Representation of a cdap resource on which an operation is executed. + */ +public class OperationResource { + private final String resourceUri; + + public OperationResource(String resourceUri) { + this.resourceUri = resourceUri; + } + + public String getResourceUri() { + return resourceUri; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OperationResource that = (OperationResource) o; + + return Objects.equals(this.resourceUri, that.resourceUri); + } + + @Override + public int hashCode() { + return Objects.hash(resourceUri); + } +} diff --git a/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResourceScopedError.java b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResourceScopedError.java new file mode 100644 index 000000000000..ead09ff116cb --- /dev/null +++ b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationResourceScopedError.java @@ -0,0 +1,60 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.proto.operationrun; + +import java.util.Objects; + +/** + * Error scoped to a single resource of the operation. + */ +public class OperationResourceScopedError { + private final String resourceUri; + private final String message; + + public OperationResourceScopedError(String resourceUri, String message) { + this.resourceUri = resourceUri; + this.message = message; + } + + public String getResourceUri() { + return resourceUri; + } + + public String getMessage() { + return message; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OperationResourceScopedError that = (OperationResourceScopedError) o; + + return Objects.equals(this.resourceUri, that.resourceUri) + && Objects.equals(this.message, that.message); + } + + @Override + public int hashCode() { + return Objects.hash(resourceUri, message); + } +} diff --git a/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRun.java b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRun.java new file mode 100644 index 000000000000..f8e821660414 --- /dev/null +++ b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRun.java @@ -0,0 +1,192 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.proto.operationrun; + +import com.google.gson.annotations.SerializedName; +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * This class records information for a particular operation run. + */ +public class OperationRun { + + @SerializedName("id") + private final String id; + + @SerializedName("type") + private final String type; + + // derived from the operation status + @SerializedName("done") + private final boolean done; + + @SerializedName("status") + private final OperationRunStatus status; + + @SerializedName("metadata") + private final OperationMeta metadata; + + // operation run error if failed + @Nullable + @SerializedName("error") + private final OperationError error; + + /** + * Constructor for OperationRun. + */ + protected OperationRun(String id, String type, OperationRunStatus status, OperationMeta metadata, + @Nullable OperationError error) { + this.id = id; + this.type = type; + this.done = getStatus().isEndState(); + this.status = status; + this.metadata = metadata; + this.error = error; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OperationRun that = (OperationRun) o; + + return Objects.equals(this.id, that.id) + && Objects.equals(this.type, that.type) + && Objects.equals(this.status, that.status) + && Objects.equals(this.metadata, that.metadata) + && Objects.equals(this.error, that.error); + } + + @Override + public int hashCode() { + return Objects.hash(id, type, status, metadata, error); + } + + /** + * Creates a OperationRun Builder. + */ + public static Builder runBuilder() { + return new Builder(); + } + + /** + * Create a OperationRun Builder from existing run. + * + * @param operationRun existing record to copy fields from + */ + public static Builder runBuilder(OperationRun operationRun) { + return new Builder(operationRun); + } + + public String getId() { + return id; + } + + public OperationRunStatus getStatus() { + return status; + } + + public OperationMeta getMetadata() { + return metadata; + } + + @Nullable + public OperationError getError() { + return error; + } + + public String getType() { + return type; + } + + /** + * Builder to create OperationRun. + * + * @param type of builder + */ + @SuppressWarnings("unchecked") + public static class Builder { + + protected String runId; + protected String type; + protected OperationRunStatus status; + protected OperationMeta metadata; + protected OperationError error; + + protected Builder() { + } + + protected Builder(OperationRun other) { + runId = other.getId(); + type = other.getType(); + status = other.getStatus(); + metadata = other.getMetadata(); + error = other.getError(); + } + + public T setStatus(OperationRunStatus status) { + this.status = status; + return (T) this; + } + + public T setRunId(String runId) { + this.runId = runId; + return (T) this; + } + + public T setMetadata(OperationMeta metadata) { + this.metadata = metadata; + return (T) this; + } + + public T setError(OperationError error) { + this.error = error; + return (T) this; + } + + public T setType(String type) { + this.type = type; + return (T) this; + } + + /** + * Validates input and returns a OperationRun. + */ + public OperationRun build() { + if (runId == null) { + throw new IllegalArgumentException("Operation run id must be specified."); + } + if (type == null) { + throw new IllegalArgumentException("Operation run type must be specified."); + } + if (metadata == null) { + throw new IllegalArgumentException("Operation run metadata must be specified."); + } + if (status == null) { + throw new IllegalArgumentException("Operation run status must be specified."); + } + return new OperationRun(runId, type, status, metadata, error); + } + } +} diff --git a/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRunStatus.java b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRunStatus.java new file mode 100644 index 000000000000..0ae7f7401744 --- /dev/null +++ b/cdap-proto/src/main/java/io/cdap/cdap/proto/operationrun/OperationRunStatus.java @@ -0,0 +1,98 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.cdap.proto.operationrun; + +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Status of operation run. + */ +public enum OperationRunStatus { + PENDING, + STARTING, + RUNNING, + STOPPING, + SUCCEEDED, + FAILED, + KILLED; + + private static final Set UNSUCCESSFUL_STATES = EnumSet.of(FAILED, KILLED); + private static final Set END_STATES = EnumSet.of(SUCCEEDED, FAILED, KILLED); + private static final Set END_STATE_NAMES = + END_STATES.stream().map(OperationRunStatus::name).collect(Collectors.toSet()); + + /** + * Return whether this state can transition to the specified state. + * + * @param status the state to transition to + * @return whether this state can transition to the specified state + */ + public boolean canTransitionTo(OperationRunStatus status) { + if (this == status) { + return true; + } + switch (this) { + case STARTING: + // RUNNING is the happy path + // STOPPING happens if the run was manually stopped gracefully(may include a timeout) + // KILLED happens if the run was manually stopped + // FAILED happens if the run failed while starting + return status == RUNNING || status == STOPPING || status == KILLED || status == FAILED; + case RUNNING: + // STOPPING happens if the run was manually stopped (may include a graceful timeout) + // SUCCEEDED is the happy path + // FAILED happens if the run failed + // KILLED happens if the run was manually stopped + return status == STOPPING || status == SUCCEEDED || status == KILLED || status == FAILED; + case STOPPING: + // SUCCEEDED is the happy path + // KILLED happens if the run was manually stopped + // FAILED happens if the run failed + return status == SUCCEEDED || status == KILLED || status == FAILED; + case SUCCEEDED: + case FAILED: + case KILLED: + // these are end states + return false; + default: + throw new IllegalStateException("Invalid transition from program run state " + this); + } + } + + /** + * Checks whether the status is an end status for a program run. + */ + public boolean isEndState() { + return END_STATES.contains(this); + } + + /** + * Checks whether a name (string) represents an end status for a program run. + */ + public static boolean isEndState(String name) { + return END_STATE_NAMES.contains(name); + } + + /** + * Checks whether the status is an end status for a program run. + */ + public boolean isUnsuccessful() { + return UNSUCCESSFUL_STATES.contains(this); + } +}