|
16 | 16 |
|
17 | 17 | package io.cdap.plugin.gcp.bigquery.sink; |
18 | 18 |
|
| 19 | +import com.google.api.gax.paging.Page; |
| 20 | +import com.google.auth.Credentials; |
19 | 21 | import com.google.cloud.bigquery.BigQuery; |
20 | 22 | import com.google.cloud.bigquery.BigQueryException; |
21 | 23 | import com.google.cloud.bigquery.Dataset; |
|
29 | 31 | import com.google.cloud.bigquery.TableId; |
30 | 32 | import com.google.cloud.hadoop.io.bigquery.BigQueryFileFormat; |
31 | 33 | import com.google.cloud.kms.v1.CryptoKeyName; |
| 34 | +import com.google.cloud.storage.Blob; |
32 | 35 | import com.google.cloud.storage.Bucket; |
33 | 36 | import com.google.cloud.storage.Storage; |
34 | 37 | import com.google.cloud.storage.StorageException; |
| 38 | +import com.google.common.base.Strings; |
35 | 39 | import com.google.common.reflect.TypeToken; |
36 | 40 | import com.google.gson.Gson; |
37 | 41 | import io.cdap.cdap.api.data.schema.Schema; |
|
40 | 44 | import io.cdap.cdap.etl.api.validation.ValidationFailure; |
41 | 45 | import io.cdap.plugin.common.Asset; |
42 | 46 | import io.cdap.plugin.common.LineageRecorder; |
| 47 | +import io.cdap.plugin.gcp.bigquery.connector.BigQueryConnectorConfig; |
43 | 48 | import io.cdap.plugin.gcp.bigquery.sink.lib.BigQueryOutputConfiguration; |
44 | 49 | import io.cdap.plugin.gcp.bigquery.sink.lib.BigQueryTableFieldSchema; |
45 | 50 | import io.cdap.plugin.gcp.bigquery.sink.lib.BigQueryTableSchema; |
|
50 | 55 | import org.apache.hadoop.conf.Configuration; |
51 | 56 | import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; |
52 | 57 | import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; |
| 58 | +import org.slf4j.Logger; |
| 59 | +import org.slf4j.LoggerFactory; |
53 | 60 |
|
54 | 61 | import java.io.IOException; |
55 | 62 | import java.lang.reflect.Type; |
|
74 | 81 | */ |
75 | 82 | public final class BigQuerySinkUtils { |
76 | 83 |
|
| 84 | + private static final Logger LOG = LoggerFactory.getLogger(BigQuerySinkUtils.class); |
77 | 85 | public static final String GS_PATH_FORMAT = "gs://%s/%s"; |
78 | 86 | private static final String TEMPORARY_BUCKET_FORMAT = GS_PATH_FORMAT + "/input/%s-%s"; |
| 87 | + private static final String BQ_TEMP_BUCKET_NAME_PREFIX = "bq-sink-bucket-"; |
| 88 | + private static final String BQ_TEMP_BUCKET_NAME_TEMPLATE = BQ_TEMP_BUCKET_NAME_PREFIX + "%s"; |
| 89 | + private static final String BQ_TEMP_BUCKET_PATH_TEMPLATE = "gs://" + BQ_TEMP_BUCKET_NAME_TEMPLATE; |
79 | 90 | private static final String DATETIME = "DATETIME"; |
80 | 91 | private static final String RECORD = "RECORD"; |
81 | 92 | private static final String JSON = "JSON"; |
@@ -255,7 +266,7 @@ public static String configureBucket(Configuration baseConfiguration, @Nullable |
255 | 266 | boolean deleteBucket = false; |
256 | 267 | // If the bucket is null, assign the run ID as the bucket name and mark the bucket for deletion. |
257 | 268 | if (bucket == null) { |
258 | | - bucket = runId; |
| 269 | + bucket = String.format(BQ_TEMP_BUCKET_NAME_TEMPLATE, runId); |
259 | 270 | deleteBucket = true; |
260 | 271 | } |
261 | 272 | return configureBucket(baseConfiguration, bucket, runId, deleteBucket); |
@@ -982,4 +993,57 @@ private static void getJsonStringFieldsFromBQSchema(FieldList fieldList, |
982 | 993 | path.remove(path.size() - 1); |
983 | 994 | } |
984 | 995 | } |
| 996 | + |
| 997 | + /** |
| 998 | + * Deletes temporary GCS directory. |
| 999 | + * |
| 1000 | + * @param configuration Hadoop Configuration. |
| 1001 | + * @param bucket the bucket name |
| 1002 | + * @param runId the run ID |
| 1003 | + */ |
| 1004 | + private static void deleteGcsTemporaryDirectory(Configuration configuration, |
| 1005 | + @Nullable String bucket, String runId) { |
| 1006 | + String gcsPath; |
| 1007 | + // If the bucket was created for this run, build temp path name using the bucket path and delete the entire bucket. |
| 1008 | + if (bucket == null) { |
| 1009 | + gcsPath = String.format(BQ_TEMP_BUCKET_PATH_TEMPLATE, runId); |
| 1010 | + } else { |
| 1011 | + gcsPath = String.format(GS_PATH_FORMAT, bucket, runId); |
| 1012 | + } |
| 1013 | + |
| 1014 | + try { |
| 1015 | + BigQueryUtil.deleteTemporaryDirectory(configuration, gcsPath); |
| 1016 | + } catch (Exception e) { |
| 1017 | + LOG.warn("Failed to delete temporary directory '{}': {}", gcsPath, e.getMessage()); |
| 1018 | + } |
| 1019 | + } |
| 1020 | + |
| 1021 | + /** |
| 1022 | + * Returns the serviceAccountCredentials if present in the config, otherwise null. |
| 1023 | + */ |
| 1024 | + @Nullable |
| 1025 | + public static Credentials getCredentials(BigQueryConnectorConfig config) throws IOException { |
| 1026 | + return config.getServiceAccount() == null ? |
| 1027 | + null : GCPUtils.loadServiceAccountCredentials(config.getServiceAccount(), |
| 1028 | + config.isServiceAccountFilePath()); |
| 1029 | + } |
| 1030 | + |
| 1031 | + /** |
| 1032 | + * Cleanup temporary GCS bucket if created. |
| 1033 | + */ |
| 1034 | + public static void cleanupGcsBucket(Configuration configuration, String runId, |
| 1035 | + @Nullable String bucket, Storage storage) { |
| 1036 | + if (!Strings.isNullOrEmpty(bucket)) { |
| 1037 | + // Only need to delete the bucket if it was created for this run |
| 1038 | + deleteGcsTemporaryDirectory(configuration, bucket, runId); |
| 1039 | + return; |
| 1040 | + } |
| 1041 | + bucket = String.format(BQ_TEMP_BUCKET_NAME_TEMPLATE, runId); |
| 1042 | + |
| 1043 | + try { |
| 1044 | + BigQueryUtil.deleteGcsBucket(storage, bucket); |
| 1045 | + } catch (Exception e) { |
| 1046 | + LOG.warn("Failed to delete GCS bucket '{}': {}", bucket, e.getMessage(), e); |
| 1047 | + } |
| 1048 | + } |
985 | 1049 | } |
0 commit comments