Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/src/main/sphinx/object-storage/file-system-gcs.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ Cloud Storage:
- Description
* - `gcs.use-access-token`
- Flag to set usage of a client-provided OAuth 2.0 token to access Google
Cloud Storage. Defaults to `false`.
Cloud Storage. Defaults to `false`, deprecated to use `gcs.auth-type` instead.
* - `gcs.auth-type`
- Authentication type to use for Google Cloud Storage access. Default to `DEFAULT`.
Supported values are:
* `DEFAULT`: loads credentials from the environment. Either `gcs.json-key` or
`gcs.json-key-file-path` can be set in addition to override the default
credentials provider.
* `ACCESS_TOKEN`: usage of client-provided OAuth 2.0 token to access Google
Cloud Storage.
* - `gcs.json-key`
- Your Google Cloud service account key in JSON format. Not to be set together
with `gcs.json-key-file-path`.
Expand Down Expand Up @@ -103,7 +111,7 @@ Cloud Storage, make the following edits to your catalog configuration:
- Native property
- Notes
* - `hive.gcs.use-access-token`
- `gcs.use-access-token`
- `gcs.auth-type`
-
* - `hive.gcs.json-key-file-path`
- `gcs.json-key-file-path`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static io.airlift.units.DataSize.Unit.MEGABYTE;

public class GcsFileSystemConfig
{
public enum AuthType
{
ACCESS_TOKEN,
DEFAULT;
}

private DataSize readBlockSize = DataSize.of(2, MEGABYTE);
private DataSize writeBlockSize = DataSize.of(16, MEGABYTE);
private int pageSize = 100;
Expand All @@ -41,7 +48,8 @@ public class GcsFileSystemConfig
private String projectId;
private Optional<String> endpoint = Optional.empty();

private boolean useGcsAccessToken;
private Optional<Boolean> useGcsAccessToken = Optional.empty();
private Optional<AuthType> authType = Optional.empty();
private String jsonKey;
private String jsonKeyFilePath;
private int maxRetries = 20;
Expand Down Expand Up @@ -134,18 +142,46 @@ public GcsFileSystemConfig setEndpoint(Optional<String> endpoint)
return this;
}

@NotNull
public AuthType getAuthType()
{
if (useGcsAccessToken.isPresent() && useGcsAccessToken.get()) {
return AuthType.ACCESS_TOKEN;
}
return authType.orElse(AuthType.DEFAULT);
}

@Config("gcs.auth-type")
public GcsFileSystemConfig setAuthType(AuthType authType)
{
this.authType = Optional.of(authType);
return this;
}

@Deprecated
public boolean isUseGcsAccessToken()
{
return useGcsAccessToken;
// should not be called; allow calling from TestGcsFileSystemConfig as it is needed by airlift config test framework
if (Arrays.stream(Thread.currentThread().getStackTrace()).anyMatch(element -> element.getClassName().equals("io.trino.filesystem.gcs.TestGcsFileSystemConfig"))) {
return useGcsAccessToken.orElse(false);
}
throw new RuntimeException("isUseGcsAccessToken must not be called; use getAuthType instead");
}

@Deprecated
@Config("gcs.use-access-token")
public GcsFileSystemConfig setUseGcsAccessToken(boolean useGcsAccessToken)
{
this.useGcsAccessToken = useGcsAccessToken;
this.useGcsAccessToken = Optional.of(useGcsAccessToken);
return this;
}

@AssertTrue(message = "Cannot set both gcs.use-access-token and gcs.auth-type")
public boolean isAuthTypeAndUseGcsAccessTokenMutuallyExclusive()
{
return !(authType.isPresent() && useGcsAccessToken.isPresent());
}

@Nullable
public String getJsonKey()
{
Expand Down Expand Up @@ -268,10 +304,10 @@ public boolean isRetryDelayValid()
return minBackoffDelay.compareTo(maxBackoffDelay) <= 0;
}

@AssertTrue(message = "Either gcs.use-access-token or gcs.json-key or gcs.json-key-file-path must be set")
@AssertTrue(message = "Either gcs.auth-type or gcs.json-key or gcs.json-key-file-path must be set")
public boolean isAuthMethodValid()
{
if (useGcsAccessToken) {
if (getAuthType() == AuthType.ACCESS_TOKEN) {
return jsonKey == null && jsonKeyFilePath == null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import com.google.inject.Scopes;
import io.airlift.configuration.AbstractConfigurationAwareModule;

import static io.airlift.configuration.ConditionalModule.conditionalModule;
import static io.airlift.configuration.ConfigBinder.configBinder;
import static io.airlift.configuration.SwitchModule.switchModule;

public class GcsFileSystemModule
extends AbstractConfigurationAwareModule
Expand All @@ -30,10 +30,12 @@ protected void setup(Binder binder)
binder.bind(GcsStorageFactory.class).in(Scopes.SINGLETON);
binder.bind(GcsFileSystemFactory.class).in(Scopes.SINGLETON);

install(conditionalModule(
install(switchModule(
GcsFileSystemConfig.class,
GcsFileSystemConfig::isUseGcsAccessToken,
_ -> binder.bind(GcsAuth.class).to(GcsAccessTokenAuth.class).in(Scopes.SINGLETON),
_ -> binder.bind(GcsAuth.class).to(GcsDefaultAuth.class).in(Scopes.SINGLETON)));
GcsFileSystemConfig::getAuthType,
type -> switch (type) {
case ACCESS_TOKEN -> _ -> binder.bind(GcsAuth.class).to(GcsAccessTokenAuth.class).in(Scopes.SINGLETON);
case DEFAULT -> _ -> binder.bind(GcsAuth.class).to(GcsDefaultAuth.class).in(Scopes.SINGLETON);
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableMap;
import io.airlift.units.DataSize;
import io.airlift.units.Duration;
import io.trino.filesystem.gcs.GcsFileSystemConfig.AuthType;
import jakarta.validation.constraints.AssertTrue;
import org.junit.jupiter.api.Test;

Expand All @@ -30,32 +31,74 @@
import static io.airlift.units.DataSize.Unit.MEGABYTE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class TestGcsFileSystemConfig
{
@Test
void testDefaults()
{
assertRecordedDefaults(recordDefaults(GcsFileSystemConfig.class)
.setReadBlockSize(DataSize.of(2, MEGABYTE))
.setWriteBlockSize(DataSize.of(16, MEGABYTE))
.setPageSize(100)
.setBatchSize(100)
.setProjectId(null)
.setEndpoint(Optional.empty())
.setUseGcsAccessToken(false)
.setJsonKey(null)
.setJsonKeyFilePath(null)
.setMaxRetries(20)
.setBackoffScaleFactor(3.0)
.setMaxRetryTime(new Duration(25, SECONDS))
.setMinBackoffDelay(new Duration(10, MILLISECONDS))
.setMaxBackoffDelay(new Duration(2000, MILLISECONDS))
.setApplicationId("Trino"));
assertThatThrownBy(
() -> assertRecordedDefaults(recordDefaults(GcsFileSystemConfig.class)
.setReadBlockSize(DataSize.of(2, MEGABYTE))
.setWriteBlockSize(DataSize.of(16, MEGABYTE))
.setPageSize(100)
.setBatchSize(100)
.setProjectId(null)
.setEndpoint(Optional.empty())
.setAuthType(AuthType.DEFAULT)
.setJsonKey(null)
.setJsonKeyFilePath(null)
.setMaxRetries(20)
.setBackoffScaleFactor(3.0)
.setMaxRetryTime(new Duration(25, SECONDS))
.setMinBackoffDelay(new Duration(10, MILLISECONDS))
.setMaxBackoffDelay(new Duration(2000, MILLISECONDS))
.setApplicationId("Trino")))
.isInstanceOf(AssertionError.class)
// use-access-token is now deprecated and isn't allowed to be set with auth-type
.hasMessage("Untested attributes: [UseGcsAccessToken]");
}

@Test
void testExplicitPropertyMappings()
{
Map<String, String> properties = ImmutableMap.<String, String>builder()
.put("gcs.read-block-size", "51MB")
.put("gcs.write-block-size", "52MB")
.put("gcs.page-size", "10")
.put("gcs.batch-size", "11")
.put("gcs.project-id", "project")
.put("gcs.endpoint", "http://custom.dns.org:8000")
.put("gcs.auth-type", "access_token")
.put("gcs.client.max-retries", "10")
.put("gcs.client.backoff-scale-factor", "4.0")
.put("gcs.client.max-retry-time", "10s")
.put("gcs.client.min-backoff-delay", "20ms")
.put("gcs.client.max-backoff-delay", "20ms")
.put("gcs.application-id", "application id")
.buildOrThrow();

GcsFileSystemConfig expected = new GcsFileSystemConfig()
.setReadBlockSize(DataSize.of(51, MEGABYTE))
.setWriteBlockSize(DataSize.of(52, MEGABYTE))
.setPageSize(10)
.setBatchSize(11)
.setProjectId("project")
.setEndpoint(Optional.of("http://custom.dns.org:8000"))
.setAuthType(AuthType.ACCESS_TOKEN)
.setMaxRetries(10)
.setBackoffScaleFactor(4.0)
.setMaxRetryTime(new Duration(10, SECONDS))
.setMinBackoffDelay(new Duration(20, MILLISECONDS))
.setMaxBackoffDelay(new Duration(20, MILLISECONDS))
.setApplicationId("application id");
assertFullMapping(properties, expected, Set.of("gcs.json-key", "gcs.json-key-file-path", "gcs.use-access-token"));
}

// backwards compatibility test, remove if use-access-token is removed
@Test
void testExplicitPropertyMappingsWithDeprecatedUseAccessToken()
{
Map<String, String> properties = ImmutableMap.<String, String>builder()
.put("gcs.read-block-size", "51MB")
Expand Down Expand Up @@ -87,34 +130,34 @@ void testExplicitPropertyMappings()
.setMinBackoffDelay(new Duration(20, MILLISECONDS))
.setMaxBackoffDelay(new Duration(20, MILLISECONDS))
.setApplicationId("application id");
assertFullMapping(properties, expected, Set.of("gcs.json-key", "gcs.json-key-file-path"));
assertFullMapping(properties, expected, Set.of("gcs.json-key", "gcs.json-key-file-path", "gcs.auth-type"));
}

@Test
public void testValidation()
{
assertFailsValidation(
new GcsFileSystemConfig()
.setUseGcsAccessToken(true)
.setAuthType(AuthType.ACCESS_TOKEN)
.setJsonKey("{}}"),
"authMethodValid",
"Either gcs.use-access-token or gcs.json-key or gcs.json-key-file-path must be set",
"Either gcs.auth-type or gcs.json-key or gcs.json-key-file-path must be set",
AssertTrue.class);

assertFailsValidation(
new GcsFileSystemConfig()
.setUseGcsAccessToken(true)
.setAuthType(AuthType.ACCESS_TOKEN)
.setJsonKeyFilePath("/dev/null"),
"authMethodValid",
"Either gcs.use-access-token or gcs.json-key or gcs.json-key-file-path must be set",
"Either gcs.auth-type or gcs.json-key or gcs.json-key-file-path must be set",
AssertTrue.class);

assertFailsValidation(
new GcsFileSystemConfig()
.setJsonKey("{}")
.setJsonKeyFilePath("/dev/null"),
"authMethodValid",
"Either gcs.use-access-token or gcs.json-key or gcs.json-key-file-path must be set",
"Either gcs.auth-type or gcs.json-key or gcs.json-key-file-path must be set",
AssertTrue.class);

assertFailsValidation(
Expand All @@ -125,5 +168,37 @@ public void testValidation()
"retryDelayValid",
"gcs.client.min-backoff-delay must be less than or equal to gcs.client.max-backoff-delay",
AssertTrue.class);

assertFailsValidation(
new GcsFileSystemConfig()
.setAuthType(AuthType.ACCESS_TOKEN)
.setUseGcsAccessToken(true),
"authTypeAndUseGcsAccessTokenMutuallyExclusive",
"Cannot set both gcs.use-access-token and gcs.auth-type",
AssertTrue.class);

assertFailsValidation(
new GcsFileSystemConfig()
.setAuthType(AuthType.ACCESS_TOKEN)
.setUseGcsAccessToken(false),
"authTypeAndUseGcsAccessTokenMutuallyExclusive",
"Cannot set both gcs.use-access-token and gcs.auth-type",
AssertTrue.class);

assertFailsValidation(
new GcsFileSystemConfig()
.setAuthType(AuthType.DEFAULT)
.setUseGcsAccessToken(true),
"authTypeAndUseGcsAccessTokenMutuallyExclusive",
"Cannot set both gcs.use-access-token and gcs.auth-type",
AssertTrue.class);

assertFailsValidation(
new GcsFileSystemConfig()
.setAuthType(AuthType.DEFAULT)
.setUseGcsAccessToken(false),
"authTypeAndUseGcsAccessTokenMutuallyExclusive",
"Cannot set both gcs.use-access-token and gcs.auth-type",
AssertTrue.class);
}
}