Skip to content

Commit faec3cf

Browse files
author
Komal Yadav
committed
Collecting response code for Dataproc operations
Updated Updated Updated Updated Updated Updated Updated
1 parent 156cb17 commit faec3cf

7 files changed

Lines changed: 312 additions & 5 deletions

File tree

cdap-runtime-ext-dataproc/src/main/java/io/cdap/cdap/runtime/spi/common/DataprocMetric.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ public class DataprocMetric {
3232
private final LaunchMode launchMode;
3333
@Nullable
3434
private final String imageVersion;
35+
@Nullable
36+
private final String opType;
3537

3638
private DataprocMetric(String metricName, String region, @Nullable Exception exception,
37-
@Nullable LaunchMode launchMode, @Nullable String imageVersion) {
39+
@Nullable LaunchMode launchMode, @Nullable String imageVersion, @Nullable String opType) {
3840
this.metricName = metricName;
3941
this.region = region;
4042
this.exception = exception;
4143
this.launchMode = launchMode;
4244
this.imageVersion = imageVersion;
45+
this.opType = opType;
4346
}
4447

4548
public String getMetricName() {
@@ -72,6 +75,11 @@ public LaunchMode getLaunchMode() {
7275
return launchMode;
7376
}
7477

78+
@Nullable
79+
public String getOpType() {
80+
return opType;
81+
}
82+
7583
/**
7684
* Returns a builder to create a DataprocMetric.
7785
*
@@ -93,6 +101,8 @@ public static class Builder {
93101
private Exception exception;
94102
@Nullable
95103
private LaunchMode launchMode;
104+
@Nullable
105+
private String opType;
96106

97107
private Builder(String metricName) {
98108
this.metricName = metricName;
@@ -118,6 +128,11 @@ public Builder setImageVersion(String imageVersion) {
118128
return this;
119129
}
120130

131+
public Builder setOpType(@Nullable String opType) {
132+
this.opType = opType;
133+
return this;
134+
}
135+
121136
/**
122137
* Returns a DataprocMetric.
123138
*
@@ -128,7 +143,7 @@ public DataprocMetric build() {
128143
// region should always be set unless there is a bug in the code
129144
throw new IllegalStateException("Dataproc metric is missing the region");
130145
}
131-
return new DataprocMetric(metricName, region, exception, launchMode, imageVersion);
146+
return new DataprocMetric(metricName, region, exception, launchMode, imageVersion, opType);
132147
}
133148
}
134149
}

cdap-runtime-ext-dataproc/src/main/java/io/cdap/cdap/runtime/spi/common/DataprocUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ public static void emitMetric(ProvisionerContext context, DataprocMetric datapro
397397
if (!Strings.isNullOrEmpty(dataprocMetric.getImageVersion())) {
398398
tags.put("imgVer", dataprocMetric.getImageVersion());
399399
}
400+
if (!Strings.isNullOrEmpty(dataprocMetric.getOpType())) {
401+
tags.put("method", dataprocMetric.getOpType());
402+
}
400403
ProvisionerMetrics metrics = context.getMetrics(tags.build());
401404
metrics.count(dataprocMetric.getMetricName(), 1);
402405
}

cdap-runtime-ext-dataproc/src/main/java/io/cdap/cdap/runtime/spi/provisioner/dataproc/DataprocClient.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import java.util.Collection;
7474
import java.util.Collections;
7575
import java.util.HashMap;
76+
import java.util.Iterator;
7677
import java.util.List;
7778
import java.util.Map;
7879
import java.util.Objects;
@@ -822,6 +823,22 @@ String getClusterFailureMsg(String name) throws RetryableProvisionException {
822823
return "";
823824
}
824825

826+
/**
827+
* Get the latest operation by cluster name and operation type.
828+
*/
829+
public Optional<Operation> getLatestOperation(String clusterName, String operationType) throws ApiException {
830+
String projectId = conf.getProjectId();
831+
String region = conf.getRegion();
832+
String resourceName = String.format("projects/%s/regions/%s/operations", projectId, region);
833+
String filter = String.format("clusterName=\"%s\" AND operationType=\"%s\"", clusterName, operationType);
834+
835+
OperationsClient.ListOperationsPagedResponse response =
836+
client.getOperationsClient().listOperations(resourceName, filter);
837+
838+
Iterator<Operation> iterator = response.getPage().getValues().iterator();
839+
return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty();
840+
}
841+
825842
/**
826843
* Get information about the specified cluster. The cluster will not be present if it could not be
827844
* found.

cdap-runtime-ext-dataproc/src/main/java/io/cdap/cdap/runtime/spi/provisioner/dataproc/DataprocProvisioner.java

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616

1717
package io.cdap.cdap.runtime.spi.provisioner.dataproc;
1818

19+
import com.google.api.gax.grpc.GrpcStatusCode;
20+
import com.google.api.gax.rpc.ApiException;
21+
import com.google.api.gax.rpc.StatusCode;
1922
import com.google.cloud.dataproc.v1.ClusterOperationMetadata;
2023
import com.google.cloud.dataproc.v1.ClusterStatus.State;
2124
import com.google.common.annotations.VisibleForTesting;
2225
import com.google.common.base.Stopwatch;
2326
import com.google.common.collect.ImmutableSet;
27+
import com.google.longrunning.Operation;
28+
import com.google.rpc.Status;
2429
import io.cdap.cdap.api.exception.ErrorCategory;
2530
import io.cdap.cdap.api.exception.ErrorType;
2631
import io.cdap.cdap.runtime.spi.ProgramRunInfo;
2732
import io.cdap.cdap.runtime.spi.RuntimeMonitorType;
2833
import io.cdap.cdap.runtime.spi.common.DataprocImageVersion;
34+
import io.cdap.cdap.runtime.spi.common.DataprocMetric;
2935
import io.cdap.cdap.runtime.spi.common.DataprocUtils;
3036
import io.cdap.cdap.runtime.spi.provisioner.Cluster;
3137
import io.cdap.cdap.runtime.spi.provisioner.ClusterStatus;
@@ -37,18 +43,22 @@
3743
import io.cdap.cdap.runtime.spi.ssh.SSHContext;
3844
import io.cdap.cdap.runtime.spi.ssh.SSHKeyPair;
3945
import io.cdap.cdap.runtime.spi.ssh.SSHPublicKey;
46+
import io.grpc.Status.Code;
4047
import java.util.Collections;
48+
import java.util.EnumSet;
4149
import java.util.HashMap;
4250
import java.util.List;
4351
import java.util.Map;
4452
import java.util.Optional;
53+
import java.util.Set;
4554
import java.util.concurrent.Future;
4655
import java.util.concurrent.TimeUnit;
4756
import java.util.concurrent.TimeoutException;
4857
import java.util.concurrent.atomic.AtomicBoolean;
4958
import java.util.concurrent.locks.Lock;
5059
import java.util.regex.Pattern;
5160
import javax.annotation.Nullable;
61+
5262
import org.slf4j.Logger;
5363
import org.slf4j.LoggerFactory;
5464

@@ -73,6 +83,12 @@ public class DataprocProvisioner extends AbstractDataprocProvisioner {
7383
//A lock to use for cluster reuse
7484
private static final String REUSE_LOCK = "reuse";
7585

86+
private static final Set<ClusterStatus> TRANSITIONING_STATES =
87+
EnumSet.of(ClusterStatus.CREATING, ClusterStatus.DELETING);
88+
89+
private static final Set<ClusterStatus> TERMINAL_STATES =
90+
EnumSet.of(ClusterStatus.RUNNING, ClusterStatus.FAILED, ClusterStatus.NOT_EXISTS);
91+
7692
private final DataprocClientFactory clientFactory;
7793

7894
@SuppressWarnings("WeakerAccess")
@@ -471,16 +487,21 @@ public ClusterStatus getClusterStatus(ProvisionerContext context, Cluster cluste
471487
DataprocConf conf = DataprocConf.create(createContextProperties(context));
472488
String clusterName = cluster.getName();
473489

474-
ClusterStatus status = cluster.getStatus();
490+
ClusterStatus previousStatus = cluster.getStatus();
475491
// if we are skipping the delete, need to avoid checking the real cluster status and pretend like it is deleted.
476-
if (conf.isSkipDelete() && status == ClusterStatus.DELETING) {
492+
if (conf.isSkipDelete() && previousStatus == ClusterStatus.DELETING) {
477493
return ClusterStatus.NOT_EXISTS;
478494
}
479495

496+
ClusterStatus status;
480497
try (DataprocClient client = clientFactory.create(conf, context.getErrorCategory())) {
481498
status = client.getClusterStatus(clusterName);
482499
DataprocUtils.emitMetric(context, conf.getRegion(), getImageVersion(context, conf),
483500
"provisioner.clusterStatus.response.count");
501+
502+
if (isEndState(previousStatus, status)) {
503+
emitLroMetric(context, client, conf, previousStatus, clusterName);
504+
}
484505
return status;
485506
} catch (Exception e) {
486507
DataprocUtils.emitMetric(context, conf.getRegion(), getImageVersion(context, conf),
@@ -489,6 +510,84 @@ public ClusterStatus getClusterStatus(ProvisionerContext context, Cluster cluste
489510
}
490511
}
491512

513+
private boolean isEndState(ClusterStatus previous, ClusterStatus current) {
514+
return TRANSITIONING_STATES.contains(previous) && TERMINAL_STATES.contains(current);
515+
}
516+
517+
/**
518+
* Emits a metric for the outcome of a Long Running Operation (LRO) when a cluster
519+
* transitions to a terminal status.
520+
*
521+
* @param context the provisioner context
522+
* @param client the dataproc client
523+
* @param conf the dataproc configuration
524+
* @param previousStatus the status of the cluster before the transition
525+
* @param clusterName the name of the cluster
526+
*/
527+
private void emitLroMetric(ProvisionerContext context, DataprocClient client, DataprocConf conf,
528+
ClusterStatus previousStatus, String clusterName) {
529+
String opType = getOpType(previousStatus);
530+
if (opType == null) {
531+
return;
532+
}
533+
534+
try {
535+
Optional<Operation> opOpt =
536+
client.getLatestOperation(clusterName, opType);
537+
538+
if (!opOpt.isPresent() || !opOpt.get().getDone()) {
539+
return;
540+
}
541+
542+
Operation op = opOpt.get();
543+
String metricName = "provisioner.operation.response.count";
544+
String imageVersion = getImageVersion(context, conf);
545+
546+
DataprocMetric.Builder builder = DataprocMetric.builder(metricName)
547+
.setRegion(conf.getRegion())
548+
.setImageVersion(imageVersion)
549+
.setOpType(opType);
550+
551+
if (op.hasError()) {
552+
builder.setException(createApiException(op.getError()));
553+
}
554+
555+
DataprocUtils.emitMetric(context, builder.build());
556+
557+
} catch (Exception ex) {
558+
LOG.warn("Failed to retrieve LRO operation metadata for metric emission on cluster {}", clusterName, ex);
559+
}
560+
}
561+
562+
@Nullable
563+
private String getOpType(ClusterStatus status) {
564+
if (status == ClusterStatus.CREATING) {
565+
return "CREATE";
566+
}
567+
if (status == ClusterStatus.DELETING) {
568+
return "DELETE";
569+
}
570+
return null;
571+
}
572+
573+
private Exception createApiException(Status error) {
574+
int rpcCode = error.getCode();
575+
576+
Code grpcCode = Code.INTERNAL;
577+
578+
if (rpcCode >= 0 && rpcCode < io.grpc.Status.Code.values().length) {
579+
grpcCode = Code.values()[rpcCode];
580+
}
581+
582+
StatusCode gaxStatusCode =
583+
GrpcStatusCode.of(grpcCode);
584+
585+
ApiException apiException = new ApiException(
586+
new Exception(error.getMessage()), gaxStatusCode, false);
587+
588+
return new Exception(apiException);
589+
}
590+
492591
@Override
493592
public String getClusterFailureMsg(ProvisionerContext context, Cluster cluster) throws Exception {
494593
DataprocConf conf = DataprocConf.create(createContextProperties(context));

cdap-runtime-ext-dataproc/src/test/java/io/cdap/cdap/runtime/spi/provisioner/dataproc/DataprocClientTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import java.util.HashMap;
6060
import java.util.List;
6161
import java.util.Map;
62+
import java.util.Optional;
6263
import java.util.concurrent.ExecutionException;
6364
import org.hamcrest.core.IsInstanceOf;
6465
import org.junit.Assert;
@@ -378,4 +379,51 @@ public void testGetClusterStatusCapturesErrorMessage() throws GeneralSecurityExc
378379
String errorMsgRet = client.getClusterFailureMsg(cdapCluster.getName());
379380
Assert.assertTrue(errorMsgRet.contains(errorMsg));
380381
}
382+
383+
@Test
384+
public void testGetLatestOperation() throws Exception {
385+
DataprocClient client = sshDataprocClientFactory.create(dataprocConf,
386+
new ErrorCategory(ErrorCategoryEnum.OTHERS));
387+
388+
OperationsClient operationsClient = PowerMockito.mock(OperationsClient.class);
389+
PowerMockito.when(clusterControllerClientMock.getOperationsClient()).thenReturn(operationsClient);
390+
391+
OperationsClient.ListOperationsPagedResponse listOperationsPagedResponse =
392+
PowerMockito.mock(OperationsClient.ListOperationsPagedResponse.class);
393+
PowerMockito.when(operationsClient.listOperations(
394+
Mockito.eq("projects/dummy-project/regions/us-test1/operations"),
395+
Mockito.eq("clusterName=\"mycluster\" AND operationType=\"CREATE\"")))
396+
.thenReturn(listOperationsPagedResponse);
397+
398+
OperationsClient.ListOperationsPage page = Mockito.mock(OperationsClient.ListOperationsPage.class);
399+
Mockito.when(listOperationsPagedResponse.getPage()).thenReturn(page);
400+
401+
Operation operation = Operation.newBuilder().setName("op-name").setDone(true).build();
402+
List<Operation> operations = Collections.singletonList(operation);
403+
Mockito.when(page.getValues()).thenReturn(operations);
404+
405+
Optional<Operation> result = client.getLatestOperation("mycluster", "CREATE");
406+
Assert.assertTrue(result.isPresent());
407+
Assert.assertEquals("op-name", result.get().getName());
408+
}
409+
410+
@Test
411+
public void testGetLatestOperationThrowsException() throws Exception {
412+
DataprocClient client = sshDataprocClientFactory.create(dataprocConf,
413+
new ErrorCategory(ErrorCategoryEnum.OTHERS));
414+
415+
OperationsClient operationsClient = PowerMockito.mock(OperationsClient.class);
416+
PowerMockito.when(clusterControllerClientMock.getOperationsClient()).thenReturn(operationsClient);
417+
418+
ApiException mockException = PowerMockito.mock(ApiException.class);
419+
PowerMockito.when(operationsClient.listOperations(Mockito.anyString(), Mockito.anyString()))
420+
.thenThrow(mockException);
421+
422+
try {
423+
client.getLatestOperation("mycluster", "CREATE");
424+
Assert.fail("Expected ApiException to be thrown");
425+
} catch (ApiException e) {
426+
Assert.assertEquals(mockException, e);
427+
}
428+
}
381429
}

0 commit comments

Comments
 (0)