Skip to content

Commit 5141087

Browse files
committed
Error Management catch known errors [GCSBucketCreate]
1 parent ee6cb2e commit 5141087

File tree

3 files changed

+137
-59
lines changed

3 files changed

+137
-59
lines changed

src/main/java/io/cdap/plugin/gcp/common/GCPErrorDetailsProvider.java

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package io.cdap.plugin.gcp.common;
1818

19-
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
2019
import com.google.api.client.http.HttpResponseException;
21-
import com.google.common.base.Strings;
2220
import com.google.common.base.Throwables;
2321
import io.cdap.cdap.api.exception.ErrorCategory;
2422
import io.cdap.cdap.api.exception.ErrorCategory.ErrorCategoryEnum;
@@ -29,7 +27,6 @@
2927
import io.cdap.cdap.etl.api.exception.ErrorContext;
3028
import io.cdap.cdap.etl.api.exception.ErrorDetailsProvider;
3129

32-
import java.io.IOException;
3330
import java.util.List;
3431

3532
/**
@@ -74,49 +71,14 @@ public ProgramFailureException getExceptionDetails(Exception e, ErrorContext err
7471
*/
7572
private ProgramFailureException getProgramFailureException(HttpResponseException e,
7673
ErrorContext errorContext) {
77-
Integer statusCode = e.getStatusCode();
78-
ErrorUtils.ActionErrorPair pair = ErrorUtils.getActionErrorByStatusCode(statusCode);
79-
String errorReason = String.format("%s %s. %s", e.getStatusCode(), e.getStatusMessage(),
80-
pair.getCorrectiveAction());
81-
82-
String errorMessage = e.getMessage();
83-
String externalDocumentationLink = null;
84-
if (e instanceof GoogleJsonResponseException) {
85-
errorMessage = getErrorMessage((GoogleJsonResponseException) e);
86-
87-
externalDocumentationLink = getExternalDocumentationLink();
88-
if (!Strings.isNullOrEmpty(externalDocumentationLink)) {
89-
90-
if (!errorReason.endsWith(".")) {
91-
errorReason = errorReason + ".";
92-
}
93-
errorReason = String.format("%s For more details, see %s", errorReason,
94-
externalDocumentationLink);
95-
}
96-
}
97-
9874
return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategoryEnum.PLUGIN),
99-
errorReason, String.format(ERROR_MESSAGE_FORMAT, errorContext.getPhase(),
100-
e.getClass().getName(), errorMessage),
101-
pair.getErrorType(), true, ErrorCodeType.HTTP, statusCode.toString(),
102-
externalDocumentationLink, e);
75+
GCPErrorDetailsProviderUtil.getErrorReason(e, getExternalDocumentationLink()),
76+
String.format(ERROR_MESSAGE_FORMAT, errorContext.getPhase(), e.getClass().getName(),
77+
GCPErrorDetailsProviderUtil.getErrorMessage(e)),
78+
ErrorUtils.getActionErrorByStatusCode(e.getStatusCode()).getErrorType(), true, ErrorCodeType.HTTP,
79+
Integer.toString(e.getStatusCode()), getExternalDocumentationLink(), e);
10380
}
10481

105-
private String getErrorMessage(GoogleJsonResponseException exception) {
106-
if (!Strings.isNullOrEmpty(exception.getMessage())) {
107-
return exception.getMessage();
108-
}
109-
if (exception.getDetails() != null) {
110-
try {
111-
return exception.getDetails().toPrettyString();
112-
} catch (IOException e) {
113-
return exception.getDetails().toString();
114-
}
115-
}
116-
return exception.getMessage();
117-
}
118-
119-
12082
/**
12183
* Get a ProgramFailureException with the given error
12284
* information from {@link IllegalArgumentException}.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright © 2025 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package io.cdap.plugin.gcp.common;
18+
19+
20+
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
21+
import com.google.api.client.http.HttpResponseException;
22+
import com.google.common.base.Strings;
23+
import com.google.common.base.Throwables;
24+
import io.cdap.cdap.api.exception.ErrorCategory;
25+
import io.cdap.cdap.api.exception.ErrorCodeType;
26+
import io.cdap.cdap.api.exception.ErrorType;
27+
import io.cdap.cdap.api.exception.ErrorUtils;
28+
import io.cdap.cdap.api.exception.ProgramFailureException;
29+
30+
import java.io.IOException;
31+
import java.util.List;
32+
33+
/**
34+
* Common functions for GCP error details provider related functionalities.
35+
*/
36+
public final class GCPErrorDetailsProviderUtil {
37+
38+
public static ProgramFailureException getHttpResponseExceptionDetailsFromChain(Exception e, String errorReason,
39+
String externalDocUrl) {
40+
return getHttpResponseExceptionDetailsFromChain(e, errorReason, ErrorType.USER, true, externalDocUrl);
41+
}
42+
43+
public static ProgramFailureException getHttpResponseExceptionDetailsFromChain(Exception e, String errorReason,
44+
ErrorType errorType,
45+
boolean dependency,
46+
String externalDocUrl) {
47+
List<Throwable> causalChain = Throwables.getCausalChain(e);
48+
for (Throwable t : causalChain) {
49+
if (t instanceof ProgramFailureException) {
50+
// Avoid double wrap
51+
return (ProgramFailureException) t;
52+
}
53+
if (t instanceof HttpResponseException) {
54+
return getProgramFailureException((HttpResponseException) t, externalDocUrl);
55+
}
56+
}
57+
// If no HttpResponseException found in the causal chain, return generic program failure exception
58+
return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN), errorReason,
59+
String.format("%s %s: %s", errorReason, e.getClass().getName(), e.getMessage()), errorType, dependency, e);
60+
}
61+
62+
private static ProgramFailureException getProgramFailureException(HttpResponseException e, String externalDocUrl) {
63+
String errorReason = getErrorReason(e, externalDocUrl);
64+
return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN), errorReason,
65+
String.format("%s %s: %s", errorReason, e.getClass().getName(), getErrorMessage(e)),
66+
ErrorUtils.getActionErrorByStatusCode(e.getStatusCode()).getErrorType(), true, ErrorCodeType.HTTP,
67+
Integer.toString(e.getStatusCode()), externalDocUrl, e);
68+
}
69+
70+
static String getErrorMessage(HttpResponseException e) {
71+
if (e instanceof GoogleJsonResponseException) {
72+
return getErrorMessage((GoogleJsonResponseException) e);
73+
}
74+
return e.getMessage();
75+
}
76+
77+
static String getErrorReason(HttpResponseException e, String externalDocUrl) {
78+
String errorReason = String.format("%s %s. %s", e.getStatusCode(), e.getStatusMessage(),
79+
ErrorUtils.getActionErrorByStatusCode(e.getStatusCode()).getCorrectiveAction());
80+
if (!Strings.isNullOrEmpty(externalDocUrl)) {
81+
if (!errorReason.endsWith(".")) {
82+
errorReason = errorReason + ".";
83+
}
84+
errorReason = String.format("%s For more details, see %s", errorReason, externalDocUrl);
85+
}
86+
return errorReason;
87+
}
88+
89+
private static String getErrorMessage(GoogleJsonResponseException exception) {
90+
if (!Strings.isNullOrEmpty(exception.getMessage())) {
91+
return exception.getMessage();
92+
}
93+
if (exception.getDetails() != null) {
94+
try {
95+
return exception.getDetails().toPrettyString();
96+
} catch (IOException e) {
97+
return exception.getDetails().toString();
98+
}
99+
}
100+
return exception.getMessage();
101+
}
102+
}

src/main/java/io/cdap/plugin/gcp/gcs/actions/GCSBucketCreate.java

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,27 @@
1616

1717
package io.cdap.plugin.gcp.gcs.actions;
1818

19-
import com.google.api.pathtemplate.ValidationException;
2019
import com.google.auth.Credentials;
21-
import com.google.auth.oauth2.ServiceAccountCredentials;
2220
import com.google.cloud.kms.v1.CryptoKeyName;
2321
import com.google.cloud.storage.Bucket;
2422
import com.google.cloud.storage.Storage;
2523
import com.google.cloud.storage.StorageException;
26-
import com.google.common.annotations.VisibleForTesting;
27-
import com.google.common.base.Strings;
2824
import io.cdap.cdap.api.annotation.Description;
2925
import io.cdap.cdap.api.annotation.Macro;
3026
import io.cdap.cdap.api.annotation.Name;
3127
import io.cdap.cdap.api.annotation.Plugin;
32-
import io.cdap.cdap.etl.api.Arguments;
28+
import io.cdap.cdap.api.exception.ErrorCategory;
29+
import io.cdap.cdap.api.exception.ErrorType;
30+
import io.cdap.cdap.api.exception.ErrorUtils;
3331
import io.cdap.cdap.etl.api.FailureCollector;
3432
import io.cdap.cdap.etl.api.PipelineConfigurer;
3533
import io.cdap.cdap.etl.api.action.Action;
3634
import io.cdap.cdap.etl.api.action.ActionContext;
3735
import io.cdap.plugin.gcp.common.CmekUtils;
3836
import io.cdap.plugin.gcp.common.GCPConfig;
37+
import io.cdap.plugin.gcp.common.GCPErrorDetailsProviderUtil;
3938
import io.cdap.plugin.gcp.common.GCPUtils;
4039
import io.cdap.plugin.gcp.gcs.GCSPath;
41-
import io.cdap.plugin.gcp.gcs.sink.GCSBatchSink;
4240
import org.apache.hadoop.conf.Configuration;
4341
import org.apache.hadoop.fs.FileSystem;
4442
import org.apache.hadoop.fs.Path;
@@ -86,9 +84,16 @@ public void run(ActionContext context) throws Exception {
8684
return;
8785
}
8886
String serviceAccount = config.getServiceAccount();
89-
Credentials credentials = serviceAccount == null ?
90-
null : GCPUtils.loadServiceAccountCredentials(serviceAccount, isServiceAccountFilePath);
91-
87+
Credentials credentials = null;
88+
try {
89+
credentials = serviceAccount == null ? null : GCPUtils.loadServiceAccountCredentials(serviceAccount,
90+
isServiceAccountFilePath);
91+
} catch (IOException e) {
92+
String errorReason = "Failed to load service account credentials.";
93+
collector.addFailure(String.format("%s %s: %s", errorReason, e.getClass().getName(), e.getMessage()), null)
94+
.withStacktrace(e.getStackTrace());
95+
collector.getOrThrowException();
96+
}
9297
Map<String, String> map = GCPUtils.generateGCSAuthProperties(serviceAccount, config.getServiceAccountType());
9398
map.forEach(configuration::set);
9499

@@ -127,17 +132,20 @@ public void run(ActionContext context) throws Exception {
127132
bucket = storage.get(gcsPath.getBucket());
128133
} catch (StorageException e) {
129134
// Add more descriptive error message
130-
throw new RuntimeException(
131-
String.format("Unable to access or create bucket %s. ", gcsPath.getBucket())
132-
+ "Ensure you entered the correct bucket path and have permissions for it.", e);
135+
String errorReason = String.format("Unable to access or create bucket %s. ", gcsPath.getBucket()) +
136+
"Ensure you entered the correct bucket path and have permissions for it.";
137+
throw GCPErrorDetailsProviderUtil.getHttpResponseExceptionDetailsFromChain(e, errorReason,
138+
GCPUtils.GCS_SUPPORTED_DOC_URL);
133139
}
134140
if (bucket == null) {
135141
GCPUtils.createBucket(storage, gcsPath.getBucket(), config.location, cmekKeyName);
136142
undoBucket.add(bucketPath);
137143
} else if (gcsPath.equals(bucketPath) && config.failIfExists()) {
138144
// if the gcs path is just a bucket, and it exists, fail the pipeline
139145
rollback = true;
140-
throw new Exception(String.format("Path %s already exists", gcsPath));
146+
String errorReason = String.format("Path %s already exists", gcsPath);
147+
throw ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN),
148+
errorReason, errorReason, ErrorType.USER, true, null);
141149
}
142150
}
143151

@@ -146,7 +154,9 @@ public void run(ActionContext context) throws Exception {
146154
fs = gcsPath.getFileSystem(configuration);
147155
} catch (IOException e) {
148156
rollback = true;
149-
throw new Exception("Unable to get GCS filesystem handler. " + e.getMessage(), e);
157+
String errorReason = "Unable to get GCS filesystem handler.";
158+
throw GCPErrorDetailsProviderUtil.getHttpResponseExceptionDetailsFromChain(e, errorReason, ErrorType.UNKNOWN,
159+
true, GCPUtils.GCS_SUPPORTED_DOC_URL);
150160
}
151161
if (!fs.exists(gcsPath)) {
152162
try {
@@ -156,12 +166,16 @@ public void run(ActionContext context) throws Exception {
156166
} catch (IOException e) {
157167
LOG.warn(String.format("Failed to create path '%s'", gcsPath));
158168
rollback = true;
159-
throw e;
169+
String errorReason = String.format("Failed to create path %s.", gcsPath);
170+
throw GCPErrorDetailsProviderUtil.getHttpResponseExceptionDetailsFromChain(e, errorReason,
171+
ErrorType.UNKNOWN, true, GCPUtils.GCS_SUPPORTED_DOC_URL);
160172
}
161173
} else {
162174
if (config.failIfExists()) {
163175
rollback = true;
164-
throw new Exception(String.format("Path %s already exists", gcsPath));
176+
String errorReason = String.format("Path %s already exists", gcsPath);
177+
throw ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN),
178+
errorReason, errorReason, ErrorType.SYSTEM, true, null);
165179
}
166180
}
167181
}

0 commit comments

Comments
 (0)