This is an automated email from the ASF dual-hosted git repository.
shunping pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 94b84b6adcb [Java] Use GcsUtil V2 by default (#40713)
94b84b6adcb is described below
commit 94b84b6adcb106b95afb50123645ae52a8d81d7a
Author: Shunping Huang <[email protected]>
AuthorDate: Thu Oct 22 17:16:33 2026 -0400
[Java] Use GcsUtil V2 by default (#40713)
* Use GcsUtil V2 by default, add disable_gcsutil_v2 fallback
GcsUtil now uses GcsUtilV2 (java-storage) by default. The
disable_gcsutil_v2 experiment falls back to GcsUtilV1 (gcsio).
use_gcsutil_v2 is still accepted but does nothing.
* Fallback to GcsUtil V1 when use_grpc_for_gcs is set
* Select GcsUtil version explicitly in ParquetIOLT
* Edited CHANGES.md
* Fix GcsUtilIT.testRewriteMultiPart to keep the V1 experiment
* Trigger Java PostCommits for GcsUtil V2 default
* Preserve kmsKeyName in GcsUtil V2 getObject. Fix test.
---
.github/trigger_files/beam_PostCommit_Java.json | 2 +-
.../beam_PostCommit_Java_DataflowV1.json | 2 +-
.../beam_PostCommit_Java_DataflowV2.json | 2 +-
...=> beam_PostCommit_Java_Examples_Dataflow.json} | 2 +-
.../beam_PostCommit_Java_Examples_Dataflow_V2.json | 2 +-
...n => beam_PostCommit_Java_Examples_Direct.json} | 2 +-
.../beam_PostCommit_Java_Hadoop_Versions.json | 2 +-
.../beam_PostCommit_Java_IO_Performance_Tests.json | 2 +-
CHANGES.md | 1 +
.../apache/beam/it/gcp/storage/ParquetIOLT.java | 40 ++++++++++------
.../sdk/extensions/gcp/options/GcsOptions.java | 19 ++++----
.../beam/sdk/extensions/gcp/util/GcsUtil.java | 40 +++++++++++++---
.../beam/sdk/extensions/gcp/util/GcsUtilV1.java | 2 +-
.../beam/sdk/extensions/gcp/util/GcsUtilIT.java | 5 +-
.../beam/sdk/extensions/gcp/util/GcsUtilTest.java | 54 ++++++++++++++++++++--
.../sdk/extensions/gcp/util/GcsUtilV1Test.java | 23 +++++++--
16 files changed, 151 insertions(+), 49 deletions(-)
diff --git a/.github/trigger_files/beam_PostCommit_Java.json
b/.github/trigger_files/beam_PostCommit_Java.json
index de320ac8499..b2a8beaf377 100644
--- a/.github/trigger_files/beam_PostCommit_Java.json
+++ b/.github/trigger_files/beam_PostCommit_Java.json
@@ -1,5 +1,5 @@
{
"https://github.com/apache/beam/pull/39893": "Fix nullness in BigQueryIO",
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 6
+ "modification": 7
}
diff --git a/.github/trigger_files/beam_PostCommit_Java_DataflowV1.json
b/.github/trigger_files/beam_PostCommit_Java_DataflowV1.json
index c403293a856..dc9b909a926 100644
--- a/.github/trigger_files/beam_PostCommit_Java_DataflowV1.json
+++ b/.github/trigger_files/beam_PostCommit_Java_DataflowV1.json
@@ -4,6 +4,6 @@
"https://github.com/apache/beam/pull/34902": "Introducing OutputBuilder",
"https://github.com/apache/beam/pull/35177": "Introducing
WindowedValueReceiver to runners",
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 9,
+ "modification": 10,
"https://github.com/apache/beam/pull/35159": "moving WindowedValue and
making an interface"
}
diff --git a/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json
b/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json
index cf92923c2d2..98161d37e42 100644
--- a/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json
+++ b/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json
@@ -1,6 +1,6 @@
{
"https://github.com/apache/beam/pull/39893": "Fix nullness in BigQueryIO",
- "modification": 9,
+ "modification": 10,
"https://github.com/apache/beam/pull/35159": "moving WindowedValue and
making an interface",
"https://github.com/apache/beam/pull/39990": "removing dead code from
FnApiDoFnRunner"
}
diff --git a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
b/.github/trigger_files/beam_PostCommit_Java_Examples_Dataflow.json
similarity index 81%
copy from .github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
copy to .github/trigger_files/beam_PostCommit_Java_Examples_Dataflow.json
index f1ba03a243e..e3d6056a5de 100644
--- a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
+++ b/.github/trigger_files/beam_PostCommit_Java_Examples_Dataflow.json
@@ -1,4 +1,4 @@
{
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 5
+ "modification": 1
}
diff --git
a/.github/trigger_files/beam_PostCommit_Java_Examples_Dataflow_V2.json
b/.github/trigger_files/beam_PostCommit_Java_Examples_Dataflow_V2.json
index 2bbe49ea608..5544cd2ef70 100644
--- a/.github/trigger_files/beam_PostCommit_Java_Examples_Dataflow_V2.json
+++ b/.github/trigger_files/beam_PostCommit_Java_Examples_Dataflow_V2.json
@@ -2,6 +2,6 @@
"https://github.com/apache/beam/pull/36138": "Cleanly separating v1 worker
and v2 sdk harness container image handling",
"https://github.com/apache/beam/pull/34902": "Introducing OutputBuilder",
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 2,
+ "modification": 3,
"https://github.com/apache/beam/pull/39990": "removing dead code from
FnApiDoFnRunner"
}
diff --git a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
b/.github/trigger_files/beam_PostCommit_Java_Examples_Direct.json
similarity index 81%
copy from .github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
copy to .github/trigger_files/beam_PostCommit_Java_Examples_Direct.json
index f1ba03a243e..e3d6056a5de 100644
--- a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
+++ b/.github/trigger_files/beam_PostCommit_Java_Examples_Direct.json
@@ -1,4 +1,4 @@
{
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 5
+ "modification": 1
}
diff --git a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
b/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
index f1ba03a243e..455144f02a3 100644
--- a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
+++ b/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json
@@ -1,4 +1,4 @@
{
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 5
+ "modification": 6
}
diff --git
a/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json
b/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json
index 12baa399f31..b2fec41d7e1 100644
--- a/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json
+++ b/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json
@@ -1,4 +1,4 @@
{
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 4,
+ "modification": 5,
}
diff --git a/CHANGES.md b/CHANGES.md
index 1f57af6cdb7..77f03f3da76 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -84,6 +84,7 @@
* (Go) Added `schema.RegisterLogicalTypeConversion` and
`schema.RegisterLogicalTypeCoder` to register portable logical types in one
call, and Go types for the standard `beam:logical_type:date:v1` and
`beam:logical_type:micros_instant:v1` logical types, so schemas from other SDKs
with these types can be used in cross-language pipelines
([#39684](https://github.com/apache/beam/issues/39684)).
* (Python, Java, Go) Added `--dataflow_container_base_repository` (Python, Go)
and `--dataflowContainerBaseRepository` (Java) to set the repository (e.g.
`docker.example.com/my-prefix/cloud-dataflow/v1beta3`) from which the Dataflow
runner pulls its default SDK container images, including the default images of
cross-language Apache Beam environments. Defaults to
`gcr.io/cloud-dataflow/v1beta3`
([#40569](https://github.com/apache/beam/issues/40569)).
* (Go) GCS and Dataflow clients now support non-default universes such as
Trusted Partner Cloud (TPC), using the universe domain from
`GOOGLE_CLOUD_UNIVERSE_DOMAIN` or the default credentials. Added a
`--gcs_endpoint` pipeline option to override the GCS endpoint
([#40569](https://github.com/apache/beam/issues/40569)).
+* (Java) `GcsUtil` now uses the java-storage client (GcsUtil V2) for GCS
operations by default instead of gcsio (GcsUtil V1). To go back to V1, pass
`--experiments=disable_gcsutil_v2`. Pipelines with the `use_grpc_for_gcs`
experiment keep using V1. The `use_gcsutil_v2` experiment is no longer needed
and is now ignored ([#40712](https://github.com/apache/beam/issues/40712)).
## Breaking Changes
diff --git
a/it/google-cloud-platform/src/test/java/org/apache/beam/it/gcp/storage/ParquetIOLT.java
b/it/google-cloud-platform/src/test/java/org/apache/beam/it/gcp/storage/ParquetIOLT.java
index 52b12372dcd..b8838a6ba24 100644
---
a/it/google-cloud-platform/src/test/java/org/apache/beam/it/gcp/storage/ParquetIOLT.java
+++
b/it/google-cloud-platform/src/test/java/org/apache/beam/it/gcp/storage/ParquetIOLT.java
@@ -132,15 +132,17 @@ import org.junit.runners.MethodSorters;
* <h3>GcsUtil v1 or v2</h3>
*
* <p>{@code useGcsUtilV2} routes GCS access through the java-storage client
and defaults to off,
- * i.e. to the gcsio based GcsUtilV1 that every pipeline uses today:
+ * i.e. to the gcsio based GcsUtilV1:
*
* <pre>
* -Dconfiguration='{"preset":"f100_s16","useGcsUtilV2":true}'
* </pre>
*
- * <p>On Dataflow this is sent as the {@code use_gcsutil_v2} experiment, for
the other runners it is
- * set on the pipeline options directly. Either way {@code GcsUtil} logs the
version it selected at
- * INFO, which is what to grep for to confirm a run really used the intended
client.
+ * <p>The version is always selected explicitly, with the {@code
use_gcsutil_v2} or the {@code
+ * disable_gcsutil_v2} experiment, so a run does not depend on which one
{@code GcsUtil} defaults
+ * to. On Dataflow the experiment is sent as a job parameter, for the other
runners it is set on the
+ * pipeline options directly. {@code GcsUtil} logs the version it selected at
INFO, which is what to
+ * grep for to confirm a run really used the intended client.
*
* <p>The number of requests a write costs is decided by the upload chunk
size, which {@code
* gcsUploadBufferSizeBytes} pins when a run needs to sweep it or to rule it
out as a variable:
@@ -207,6 +209,9 @@ public final class ParquetIOLT extends GcsIOLoadTestBase {
/** Experiment that routes GCS access through GcsUtilV2, i.e. the
java-storage client. */
private static final String GCS_UTIL_V2_EXPERIMENT = "use_gcsutil_v2";
+ /** Experiment that falls back to GcsUtilV1, i.e. the gcsio client. */
+ private static final String GCS_UTIL_DISABLE_V2_EXPERIMENT =
"disable_gcsutil_v2";
+
/** Pipeline option that pins the upload chunk size, see {@code GcsOptions}.
*/
private static final String GCS_UPLOAD_BUFFER_SIZE_OPTION =
"gcsUploadBufferSizeBytes";
@@ -467,10 +472,8 @@ public final class ParquetIOLT extends GcsIOLoadTestBase {
pipeline.getOptions().as(GcsOptions.class).setGcsPerformanceMetrics(true);
// Same reason: for a non-Dataflow runner the experiments parameter below
never reaches the
// pipeline, so the experiment has to be added to the options directly.
- if (configuration.useGcsUtilV2) {
- ExperimentalOptions.addExperiment(
- pipeline.getOptions().as(ExperimentalOptions.class),
GCS_UTIL_V2_EXPERIMENT);
- }
+ ExperimentalOptions.addExperiment(
+ pipeline.getOptions().as(ExperimentalOptions.class),
gcsUtilExperiment());
// Left unset the two clients pick different chunk sizes, which shows up
as a difference in the
// number of write requests.
if (configuration.gcsUploadBufferSizeBytes > 0) {
@@ -539,12 +542,18 @@ public final class ParquetIOLT extends GcsIOLoadTestBase {
private static String dataflowExperiments() {
List<String> experiments = new ArrayList<>();
experiments.add(configuration.useRunnerV2 ? RUNNER_V2_EXPERIMENT :
LEGACY_WORKER_EXPERIMENT);
- if (configuration.useGcsUtilV2) {
- experiments.add(GCS_UTIL_V2_EXPERIMENT);
- }
+ experiments.add(gcsUtilExperiment());
return String.join(",", experiments);
}
+ /**
+ * The experiment that selects the GcsUtil version under test. Both versions
are named explicitly,
+ * so that a run is not affected by which one {@code GcsUtil} picks by
default.
+ */
+ private static String gcsUtilExperiment() {
+ return configuration.useGcsUtilV2 ? GCS_UTIL_V2_EXPERIMENT :
GCS_UTIL_DISABLE_V2_EXPERIMENT;
+ }
+
/**
* Resolves {@code -Dconfiguration} into a {@link Configuration}.
*
@@ -892,11 +901,12 @@ public final class ParquetIOLT extends GcsIOLoadTestBase {
@JsonProperty public boolean useRunnerV2 = true;
/**
- * {@code true} routes GCS access through GcsUtilV2, the java-storage
client, instead of the
- * default GcsUtilV1. Applies to every runner: on Dataflow it is sent as
the {@code
- * use_gcsutil_v2} experiment, elsewhere it is set on the pipeline options
directly.
+ * {@code true} routes GCS access through GcsUtilV2, the java-storage
client, {@code false}
+ * through GcsUtilV1, the gcsio client. Applies to every runner: on
Dataflow it is sent as the
+ * {@code use_gcsutil_v2} or {@code disable_gcsutil_v2} experiment,
elsewhere it is set on the
+ * pipeline options directly.
*
- * <p>Which one a run actually used is logged by {@code GcsUtil} at INFO.
+ * <p>{@code GcsUtil} logs the version a run actually used at INFO.
*/
@JsonProperty public boolean useGcsUtilV2 = false;
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java
b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java
index b5246a87d2b..88ebf73dfab 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java
@@ -174,15 +174,15 @@ public interface GcsOptions extends
ApplicationNameOptions, GcpOptions, Pipeline
/**
* Batching limit for rewrite ops which will copy data.
*
- * @deprecated Only applicable to GcsUtil V1. Not applicable to GcsUtil V2
(enabled with the
- * {@code use_gcsutil_v2} experiment), which does not batch rewrites;
use {@link
- * #getGcsMaxConcurrentRewrites} to tune copy/move concurrency there
instead.
+ * @deprecated Only applicable to GcsUtil V1 (selected with the {@code
disable_gcsutil_v2}
+ * experiment). Not applicable to GcsUtil V2 (the default), which does
not batch rewrites; use
+ * {@link #getGcsMaxConcurrentRewrites} to tune copy/move concurrency
there instead.
*/
@Deprecated
@Description(
- "Batching limit for rewrite ops which will copy data. Deprecated: NOT
applicable to GcsUtil"
- + " V2 (enabled with the use_gcsutil_v2 experiment); use
gcsMaxConcurrentRewrites"
- + " instead.")
+ "Batching limit for rewrite ops which will copy data. Deprecated: only
applicable to"
+ + " GcsUtil V1 (selected with the disable_gcsutil_v2 experiment),
NOT to GcsUtil V2 (the"
+ + " default); use gcsMaxConcurrentRewrites instead.")
@Nullable Integer getGcsRewriteDataOpBatchLimit();
/**
@@ -195,12 +195,13 @@ public interface GcsOptions extends
ApplicationNameOptions, GcpOptions, Pipeline
* Maximum number of files rewritten concurrently when copying or moving
files. If unset, a
* default of 128 is used.
*
- * <p>Only applicable to GcsUtil V2, which is enabled with the {@code
use_gcsutil_v2} experiment.
+ * <p>Only applicable to GcsUtil V2, which is the default. Ignored when
falling back to GcsUtil V1
+ * with the {@code disable_gcsutil_v2} experiment.
*/
@Description(
"Maximum number of files rewritten concurrently when copying or moving
files in GCS."
- + " If unset, defaults to 128. Only applicable to GcsUtil V2
(enabled with the"
- + " use_gcsutil_v2 experiment).")
+ + " If unset, defaults to 128. Only applicable to GcsUtil V2 (the
default); ignored"
+ + " with the disable_gcsutil_v2 experiment.")
@Nullable Integer getGcsMaxConcurrentRewrites();
void setGcsMaxConcurrentRewrites(@Nullable Integer maxConcurrentRewrites);
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java
b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java
index 382ac6856bf..25ca962711d 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java
@@ -127,16 +127,41 @@ public class GcsUtil {
return GcsPath.isWildcard(spec);
}
+ /**
+ * Experiment that falls back to GcsUtil V1 (gcsio). V2 (java-storage) is
the default, and this
+ * experiment is the escape hatch should V2 misbehave for a pipeline.
+ */
+ public static final String DISABLE_GCSUTIL_V2_EXPERIMENT =
"disable_gcsutil_v2";
+
+ /**
+ * Experiment that used to opt into GcsUtil V2. V2 is now the default, so
this is a no-op kept so
+ * that existing pipelines that still pass it keep working. {@link
#DISABLE_GCSUTIL_V2_EXPERIMENT}
+ * takes precedence if both are set.
+ */
+ public static final String USE_GCSUTIL_V2_EXPERIMENT = "use_gcsutil_v2";
+
+ /**
+ * Experiment that makes GcsUtil V1 talk to GCS over gRPC. Only V1 (gcsio)
supports it, so it also
+ * falls back to V1, the same as {@link #DISABLE_GCSUTIL_V2_EXPERIMENT}.
+ */
+ public static final String USE_GRPC_FOR_GCS_EXPERIMENT = "use_grpc_for_gcs";
+
GcsUtil(PipelineOptions options) {
this.delegate = new GcsUtilV1.GcsUtilFactory().create(options);
- if (ExperimentalOptions.hasExperiment(options, "use_gcsutil_v2")) {
+ @Nullable String v1Experiment = null;
+ if (ExperimentalOptions.hasExperiment(options,
DISABLE_GCSUTIL_V2_EXPERIMENT)) {
+ v1Experiment = DISABLE_GCSUTIL_V2_EXPERIMENT;
+ } else if (ExperimentalOptions.hasExperiment(options,
USE_GRPC_FOR_GCS_EXPERIMENT)) {
+ v1Experiment = USE_GRPC_FOR_GCS_EXPERIMENT;
+ }
+ if (v1Experiment != null) {
+ this.delegateV2 = null;
+ LOG.info(
+ "Using GcsUtilV1 (gcsio) for GCS operations because the {}
experiment is set.",
+ v1Experiment);
+ } else {
this.delegateV2 = new GcsUtilV2.GcsUtilFactory().create(options);
- // INFO only for V2, which is opt-in. V1 is still the default for every
pipeline,
- // so logging it at INFO would be noise.
LOG.info("Using GcsUtilV2 (java-storage) for GCS operations.");
- } else {
- this.delegateV2 = null;
- LOG.debug("Using GcsUtilV1 (gcsio) for GCS operations.");
}
}
@@ -633,7 +658,8 @@ public class GcsUtil {
.setContentEncoding(blob.getContentEncoding())
.setMd5Hash(blob.getMd5())
.setCrc32c(blob.getCrc32c())
- .setEtag(blob.getEtag());
+ .setEtag(blob.getEtag())
+ .setKmsKeyName(blob.getKmsKeyName());
Long size = blob.getSize();
if (size != null) {
storageObject.setSize(BigInteger.valueOf(size));
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1.java
b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1.java
index 56de6a477aa..bb5570df79c 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1.java
@@ -171,7 +171,7 @@ class GcsUtilV1 {
storageBuilder.build(),
storageBuilder.getHttpRequestInitializer(),
gcsOptions.getExecutorService(),
- hasExperiment(options, "use_grpc_for_gcs"),
+ hasExperiment(options, GcsUtil.USE_GRPC_FOR_GCS_EXPERIMENT),
gcsOptions.getGcpCredential(),
gcsOptions.getGcsUploadBufferSizeBytes(),
gcsOptions.getGcsRewriteDataOpBatchLimit(),
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilIT.java
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilIT.java
index a6ae12024a1..adb69467785 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilIT.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilIT.java
@@ -109,7 +109,7 @@ public class GcsUtilIT {
@Parameters(name = "{0}")
public static Iterable<String> data() {
- return Arrays.asList("use_gcsutil_v1", "use_gcsutil_v2");
+ return Arrays.asList("disable_gcsutil_v2", "use_gcsutil_v2");
}
@Parameter public String experiment;
@@ -1342,6 +1342,9 @@ public class GcsUtilIT {
TestPipelineOptions options =
TestPipeline.testingPipelineOptions().as(TestPipelineOptions.class);
+ // These options are built from scratch, so the experiment set in setUp()
has to be carried
+ // over, otherwise GcsUtil picks its default (V2) rather than the V1 this
test exercises.
+
options.as(ExperimentalOptions.class).setExperiments(Collections.singletonList(experiment));
// Using a KMS key is necessary to trigger multi-part rewrites (bucket is
created
// with a bucket default key).
assertNotNull(options.getTempRoot());
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java
index 12d89dec705..2501313304e 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java
@@ -43,6 +43,7 @@ import java.nio.channels.SeekableByteChannel;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@@ -52,6 +53,7 @@ import org.apache.beam.sdk.extensions.gcp.options.GcsOptions;
import
org.apache.beam.sdk.extensions.gcp.util.GcsUtil.StorageObjectOrIOException;
import org.apache.beam.sdk.extensions.gcp.util.gcsfs.GcsPath;
import org.apache.beam.sdk.io.fs.MoveOptions.StandardMoveOptions;
+import org.apache.beam.sdk.options.ExperimentalOptions;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -117,7 +119,7 @@ public class GcsUtilTest {
}
// The tests below cover the routing that the GcsUtil facade performs for
the legacy typed
- // methods once the use_gcsutil_v2 experiment installs a GcsUtilV2 delegate.
Both delegates are
+ // methods when a GcsUtilV2 delegate is installed (the default). Both
delegates are
// mocked, so they assert both that V2 is used and that V1 is left alone.
private GcsUtilV1 mockDelegate;
@@ -333,6 +335,7 @@ public class GcsUtilTest {
when(blob.getMd5()).thenReturn("md5==");
when(blob.getCrc32c()).thenReturn("crc==");
when(blob.getEtag()).thenReturn("etag");
+
when(blob.getKmsKeyName()).thenReturn("projects/p/locations/l/keyRings/r/cryptoKeys/k");
when(blob.getUpdateTimeOffsetDateTime())
.thenReturn(Instant.ofEpochMilli(1234L).atOffset(ZoneOffset.UTC));
when(blob.getCreateTimeOffsetDateTime())
@@ -351,6 +354,7 @@ public class GcsUtilTest {
assertEquals("md5==", object.getMd5Hash());
assertEquals("crc==", object.getCrc32c());
assertEquals("etag", object.getEtag());
+ assertEquals("projects/p/locations/l/keyRings/r/cryptoKeys/k",
object.getKmsKeyName());
assertEquals(1234L, object.getUpdated().getValue());
assertEquals(1000L, object.getTimeCreated().getValue());
Mockito.verifyNoMoreInteractions(mockDelegate);
@@ -439,10 +443,54 @@ public class GcsUtilTest {
assertNull(converted.getStorageClass());
}
- /** Without the use_gcsutil_v2 experiment, the V2-only methods fail rather
than fall back. */
+ private static GcsOptions gcsOptionsWithExperiments(String... experiments) {
+ GcsOptions options = gcsOptionsWithTestCredential();
+
options.as(ExperimentalOptions.class).setExperiments(Arrays.asList(experiments));
+ return options;
+ }
+
+ /** GcsUtil V2 is used by default. */
@Test
- public void testV2OnlyMethodsFailWithoutV2() {
+ public void testUsesV2ByDefault() {
GcsUtil gcsUtil = gcsOptionsWithTestCredential().getGcsUtil();
+ assertNotNull(gcsUtil.delegateV2);
+ }
+
+ /** The legacy opt-in experiment is still accepted and keeps V2. */
+ @Test
+ public void testUsesV2WithLegacyV2Experiment() {
+ GcsUtil gcsUtil =
gcsOptionsWithExperiments(GcsUtil.USE_GCSUTIL_V2_EXPERIMENT).getGcsUtil();
+ assertNotNull(gcsUtil.delegateV2);
+ }
+
+ /** The disable_gcsutil_v2 experiment falls back to V1, even when
use_gcsutil_v2 is also set. */
+ @Test
+ public void testFallsBackToV1WithDisableV2Experiment() {
+ assertNull(
+
gcsOptionsWithExperiments(GcsUtil.DISABLE_GCSUTIL_V2_EXPERIMENT).getGcsUtil().delegateV2);
+ assertNull(
+ gcsOptionsWithExperiments(
+ GcsUtil.DISABLE_GCSUTIL_V2_EXPERIMENT,
GcsUtil.USE_GCSUTIL_V2_EXPERIMENT)
+ .getGcsUtil()
+ .delegateV2);
+ }
+
+ /** gRPC is only supported by V1, so the use_grpc_for_gcs experiment falls
back to V1. */
+ @Test
+ public void testFallsBackToV1WithGrpcExperiment() {
+ assertNull(
+
gcsOptionsWithExperiments(GcsUtil.USE_GRPC_FOR_GCS_EXPERIMENT).getGcsUtil().delegateV2);
+ assertNull(
+ gcsOptionsWithExperiments(
+ GcsUtil.USE_GRPC_FOR_GCS_EXPERIMENT,
GcsUtil.USE_GCSUTIL_V2_EXPERIMENT)
+ .getGcsUtil()
+ .delegateV2);
+ }
+
+ /** With the disable_gcsutil_v2 experiment, the V2-only methods fail rather
than fall back. */
+ @Test
+ public void testV2OnlyMethodsFailWithoutV2() {
+ GcsUtil gcsUtil =
gcsOptionsWithExperiments(GcsUtil.DISABLE_GCSUTIL_V2_EXPERIMENT).getGcsUtil();
assertNull(gcsUtil.delegateV2);
GcsUtilV1 v1 = Mockito.mock(GcsUtilV1.class);
gcsUtil.delegate = v1;
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1Test.java
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1Test.java
index 72d554da752..2295ecdd1cf 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1Test.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV1Test.java
@@ -105,6 +105,7 @@ import
org.apache.beam.sdk.extensions.gcp.util.gcsfs.GcsPath;
import org.apache.beam.sdk.io.fs.MoveOptions.StandardMoveOptions;
import org.apache.beam.sdk.metrics.MetricName;
import org.apache.beam.sdk.metrics.MetricsEnvironment;
+import org.apache.beam.sdk.options.ExperimentalOptions;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.util.FastNanoClockAndSleeper;
@@ -142,8 +143,20 @@ public class GcsUtilV1Test {
MetricsEnvironment.setCurrentContainer(null);
}
- private static GcsOptions gcsOptionsWithTestCredential() {
+ /**
+ * These tests exercise {@link GcsUtilV1}, which is no longer the default,
so every {@link
+ * GcsUtil} they build must opt into it with the {@code disable_gcsutil_v2}
experiment.
+ */
+ private static GcsOptions gcsOptionsUsingV1() {
GcsOptions pipelineOptions = PipelineOptionsFactory.as(GcsOptions.class);
+ pipelineOptions
+ .as(ExperimentalOptions.class)
+
.setExperiments(Collections.singletonList(GcsUtil.DISABLE_GCSUTIL_V2_EXPERIMENT));
+ return pipelineOptions;
+ }
+
+ private static GcsOptions gcsOptionsWithTestCredential() {
+ GcsOptions pipelineOptions = gcsOptionsUsingV1();
pipelineOptions.setGcpCredential(new TestCredential());
return pipelineOptions;
}
@@ -181,7 +194,7 @@ public class GcsUtilV1Test {
.setFastFailOnNotFoundEnabled(false)
.build();
- GcsOptions pipelineOptions = PipelineOptionsFactory.as(GcsOptions.class);
+ GcsOptions pipelineOptions = gcsOptionsUsingV1();
pipelineOptions.setGoogleCloudStorageReadOptions(readOptions);
GcsUtil gcsUtil = pipelineOptions.getGcsUtil();
@@ -1746,7 +1759,7 @@ public class GcsUtilV1Test {
private void testWriteMetrics(boolean enabled) throws IOException {
// arrange
- GcsOptions gcsOptions =
PipelineOptionsFactory.create().as(GcsOptions.class);
+ GcsOptions gcsOptions = gcsOptionsUsingV1();
gcsOptions.setEnableBucketWriteMetricCounter(enabled);
gcsOptions.setGcsWriteCounterPrefix("test_counter");
GcsUtilMock gcsUtil = GcsUtilMock.createMockWithMockStorage(gcsOptions,
null);
@@ -1774,7 +1787,7 @@ public class GcsUtilV1Test {
private void testReadMetrics(boolean enabled, GoogleCloudStorageReadOptions
readOptions)
throws IOException {
// arrange
- GcsOptions gcsOptions =
PipelineOptionsFactory.create().as(GcsOptions.class);
+ GcsOptions gcsOptions = gcsOptionsUsingV1();
gcsOptions.setEnableBucketReadMetricCounter(enabled);
gcsOptions.setGcsReadCounterPrefix("test_counter");
if (readOptions != null) {
@@ -1833,7 +1846,7 @@ public class GcsUtilV1Test {
@Test
public void testGcsEndpoint() throws IOException {
- GcsOptions pipelineOptions = PipelineOptionsFactory.as(GcsOptions.class);
+ GcsOptions pipelineOptions = gcsOptionsUsingV1();
pipelineOptions.setGcsEndpoint("http://localhost:4443/storage/v1/");
GcsUtil gcsUtil = pipelineOptions.getGcsUtil();