This is an automated email from the ASF dual-hosted git repository.
diqiu50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new acba664456 [#12542] improvement(trino-connector): Support TLS and
session role for the internal JDBC connection (#12543)
acba664456 is described below
commit acba664456c104011b2c183d9663d0cf6bc3a1a4
Author: Yuhui <[email protected]>
AuthorDate: Mon Aug 24 20:27:35 2026 +0800
[#12542] improvement(trino-connector): Support TLS and session role for the
internal JDBC connection (#12543)
### What changes were proposed in this pull request?
Make the internal JDBC connection to the Trino coordinator configurable:
TLS, truststore, certificate verification and session roles, plus a
`trino.jdbc.properties.` prefix for any other driver property.
Verification defaults to `FULL`, and TLS defaults to the scheme of the
Trino `discovery.uri`.
As these settings are not propagated to the catalogs the connector
creates, the catalog connector manager is now started from the static
connector only.
### Why are the changes needed?
`CatalogRegister.init()` only sets `user` and `password`, so the
connection cannot be established against a TLS-enabled or
authorization-enabled coordinator, and forking the connector was the
only workaround.
Fix: #12542
### Does this PR introduce _any_ user-facing change?
Yes, new optional configuration properties documented in
`docs/trino-connector/configuration.md`, all defaulting to the current
behavior.
`trino.jdbc.*` properties are no longer propagated into the catalogs the
connector creates, which also stops `trino.jdbc.password` from reaching
the logs and the catalog properties files.
An inconsistent TLS configuration now fails at startup instead of being
silently ignored, unless it is set through the `trino.jdbc.properties.`
escape hatch, which is applied without validation. The mutual TLS
properties are provided for completeness and have not been verified
against a coordinator that requires client certificates.
If the static Gravitino catalog is removed while Gravitino-created
catalogs remain, the manager is no longer started, so catalogs deleted
on the server side are not cleaned up.
### How was this patch tested?
New unit tests in `TestCatalogRegister`, `TestGravitinoConfig` and
`TestGravitinoConnectorFactoryStart`, plus a manual run against a
coordinator with `http-server.https.enabled` and a self-signed keystore,
where catalog registration succeeded under `SSLVerification=FULL`.
Note: `trino.jdbc.roles=system:sysadmin` cannot be verified on
open-source Trino, whose default access control rejects it.
---
docs/trino-connector/configuration.md | 125 ++++++-
.../gravitino/trino/connector/GravitinoConfig.java | 242 ++++++++++++-
.../trino/connector/GravitinoConnectorFactory.java | 49 ++-
.../connector/catalog/CatalogConnectorManager.java | 17 +-
.../trino/connector/catalog/CatalogRegister.java | 214 +++++++++++-
.../trino/connector/TestGravitinoConfig.java | 132 +++++++
.../TestGravitinoConnectorFactoryStart.java | 152 +++++++++
.../connector/catalog/TestCatalogRegister.java | 380 +++++++++++++++++++++
8 files changed, 1277 insertions(+), 34 deletions(-)
diff --git a/docs/trino-connector/configuration.md
b/docs/trino-connector/configuration.md
index 3053ef646d..5bcb98636e 100644
--- a/docs/trino-connector/configuration.md
+++ b/docs/trino-connector/configuration.md
@@ -7,18 +7,28 @@ license: "This software is licensed under the Apache License
version 2."
## Introduction
-| Property | Type | Default Value
| Description
|
Required |
-|---------------------------------------------|---------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
-| connector.name | string | (none)
| The `connector.name` defines the type of Trino connector, this value is
always 'gravitino'.
|
Yes |
-| gravitino.metalake | string | (none)
| The `gravitino.metalake` defines which metalake in Gravitino server the
Trino connector uses. Trino connector should set it at start, the value of
`gravitino.metalake` needs to be a valid name, Trino connector can detect and
load the metalake with catalogs, schemas and tables once created and keep in
sync. | Yes |
-| gravitino.uri | string |
http://localhost:8090 | The `gravitino.uri` defines the connection URL of the
Gravitino server, the default value is `http://localhost:8090`. Trino connector
can detect and connect to Gravitino server once it is ready, no need to start
Gravitino server beforehand.
| No |
-| trino.jdbc.user | string | admin
| The jdbc user name of current Trino.
| NO
|
-| trino.jdbc.password | string | (none)
| The jdbc password of current Trino.
| NO
|
-| gravitino.metadata.refresh-interval-seconds | integer | 10
| The `gravitino.metadata.refresh-interval-seconds` defines the interval in
seconds to refresh metadata from Gravitino server, the default value is 10
seconds.
| No |
-| gravitino.trino.skip-version-validation | boolean | false
| The `gravitino.trino.skip-version-validation` defines whether to skip Trino
version validation. Gravitino supports Trino versions between 435 and 478. If
this option is `true`, unsupported Trino versions can still be used, but
compatibility is not guaranteed.
| No |
-| gravitino.client. | string | (none)
| The configuration key prefix for the Gravitino client config.
| No
|
-| gravitino.trino.skip-catalog-patterns | string | (none)
| The `gravitino.trino.skip-catalog-patterns` defines a comma-separated list
of catalog name regex patterns that should be excluded from loading. For
example, `test_.*, .*_tmp` excludes all catalogs starting with `test_` or
ending with `_tmp`.
| No |
-| gravitino.use-single-metalake | boolean | true
| If `true`, only one metalake is used and catalogs are identified by
`<catalog_name>`. If `false`, multi-metalake mode is enabled and catalogs are
identified by `<metalake_name>.<catalog_name>`.
| No |
+| Property | Type | Default Value
| Description
| Required |
+|---------------------------------------------|---------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
+| connector.name | string | (none)
| The `connector.name` defines the type of Trino connector, this value is
always 'gravitino'.
| Yes |
+| gravitino.metalake | string | (none)
| The `gravitino.metalake` defines which metalake in Gravitino server the
Trino connector uses. Trino connector should set it at start, the value of
`gravitino.metalake` needs to be a valid name, Trino connector can detect and
load the metalake with catalogs, schemas and tables once created and keep in
sync. | Yes |
+| gravitino.uri | string |
http://localhost:8090 | The `gravitino.uri` defines the connection URL of the
Gravitino server, the default value is `http://localhost:8090`. Trino connector
can detect and connect to Gravitino server once it is ready, no need to start
Gravitino server beforehand.
| No |
+| trino.jdbc.user | string | admin
| The jdbc user name of current Trino.
| NO |
+| trino.jdbc.password | string | (none)
| The jdbc password of current Trino.
| NO |
+| trino.jdbc.ssl.enabled | boolean | (derived)
| Whether the internal JDBC connection to the Trino coordinator uses TLS. If
not set, it is derived from the scheme of the Trino `discovery.uri`, so a
coordinator whose `discovery.uri` is `https://...` needs no explicit setting.
| No |
+| trino.jdbc.ssl.truststore.path | string | (none)
| Path of the truststore holding the Trino coordinator certificate. If
omitted, the default JVM truststore is used. Requires TLS, which is enabled
automatically for an HTTPS `discovery.uri` or explicitly with
`trino.jdbc.ssl.enabled=true`, and a `trino.jdbc.ssl.verification` other than
`NONE`. | No |
+| trino.jdbc.ssl.truststore.password | string | (none)
| Password of the truststore configured by `trino.jdbc.ssl.truststore.path`.
Requires TLS and `trino.jdbc.ssl.truststore.path`, otherwise the connector
fails to start.
| No |
+| trino.jdbc.ssl.truststore.type | string | (none)
| Type of the truststore, for example `JKS` or `PKCS12`. If omitted, the
default JVM truststore type is used. Requires TLS and
`trino.jdbc.ssl.truststore.path`, otherwise the connector fails to start.
| No |
+| trino.jdbc.ssl.keystore.path | string | (none)
| Path of the keystore holding the client certificate presented to the
coordinator, for coordinators that require mutual TLS. Requires TLS, which is
enabled automatically for an HTTPS `discovery.uri` or explicitly with
`trino.jdbc.ssl.enabled=true`, and a `trino.jdbc.ssl.verification` other than
`NONE`. See the note on mutual TLS below. | No |
+| trino.jdbc.ssl.keystore.password | string | (none)
| Password of the keystore configured by `trino.jdbc.ssl.keystore.path`.
Requires TLS and `trino.jdbc.ssl.keystore.path`, otherwise the connector fails
to start.
| No |
+| trino.jdbc.ssl.keystore.type | string | (none)
| Type of the keystore, for example `JKS` or `PKCS12`. If omitted, the default
JVM keystore type is used. Requires TLS and `trino.jdbc.ssl.keystore.path`,
otherwise the connector fails to start.
| No |
+| trino.jdbc.ssl.verification | string | FULL
| Certificate verification mode of the internal JDBC connection: `FULL`, `CA`
or `NONE`. Any value other than `FULL` requires TLS, which may be derived from
an HTTPS `discovery.uri`. `NONE` disables certificate verification entirely and
should only be used for troubleshooting.
| No |
+| trino.jdbc.roles | string | (none)
| Session roles applied to the internal JDBC connection, for example
`system:sysadmin`. Required by deployments that only allow `CREATE CATALOG`
with a privileged role.
| No |
+| trino.jdbc.properties. | string | (none)
| The configuration key prefix for raw Trino JDBC driver properties, see
[Connecting to a TLS-enabled
coordinator](#connecting-to-a-tls-enabled-coordinator).
|
No |
+| gravitino.metadata.refresh-interval-seconds | integer | 10
| The `gravitino.metadata.refresh-interval-seconds` defines the interval in
seconds to refresh metadata from Gravitino server, the default value is 10
seconds.
| No |
+| gravitino.trino.skip-version-validation | boolean | false
| The `gravitino.trino.skip-version-validation` defines whether to skip Trino
version validation. Gravitino supports Trino versions between 435 and 478. If
this option is `true`, unsupported Trino versions can still be used, but
compatibility is not guaranteed.
| No |
+| gravitino.client. | string | (none)
| The configuration key prefix for the Gravitino client config.
| No |
+| gravitino.trino.skip-catalog-patterns | string | (none)
| The `gravitino.trino.skip-catalog-patterns` defines a comma-separated list
of catalog name regex patterns that should be excluded from loading. For
example, `test_.*, .*_tmp` excludes all catalogs starting with `test_` or
ending with `_tmp`.
| No |
+| gravitino.use-single-metalake | boolean | true
| If `true`, only one metalake is used and catalogs are identified by
`<catalog_name>`. If `false`, multi-metalake mode is enabled and catalogs are
identified by `<metalake_name>.<catalog_name>`.
| No |
To configure the Gravitino client, use properties prefixed with
`gravitino.client.`. These properties will directly passed to the Gravitino
client.
@@ -26,6 +36,97 @@ To configure the Gravitino client, use properties prefixed
with `gravitino.clien
Multi-metalake mode (`gravitino.use-single-metalake=false`) is supported on
Trino connector versions 435-445 and 469-478. On versions 446-468, a warning is
logged and the connector initializes, but the mode is not fully supported and
some operations may fail.
+## Connecting to a TLS-enabled coordinator
+
+The Gravitino Trino connector registers catalogs by connecting back to the
Trino coordinator over
+JDBC and running `CREATE CATALOG` / `DROP CATALOG`. This connection is
established when the
+connector starts and is reused by the metadata refresh loop, so it must be
configured for the
+coordinator's own TLS and authorization settings.
+
+```properties
+connector.name=gravitino
+gravitino.metalake=metalake
+gravitino.uri=http://localhost:8090
+
+# The internal JDBC connection to the coordinator.
+trino.jdbc.user=admin
+trino.jdbc.password=YourSecureTrinoPassword
+trino.jdbc.ssl.truststore.path=/etc/trino/truststore.jks
+trino.jdbc.ssl.truststore.password=YourSecureTruststorePassword
+# Required when the deployment only allows CREATE CATALOG with a privileged
role.
+trino.jdbc.roles=system:sysadmin
+```
+
+`trino.jdbc.ssl.enabled` may be omitted when the Trino `discovery.uri` uses
the `https` scheme, as
+it is derived from that scheme by default. When `discovery.uri` omits the
port, the default port of
+its scheme is used, that is `443` for `https` and `80` for `http`.
+
+The `trino.jdbc.ssl.*` configurations are only meaningful on a TLS-enabled
connection. Setting any
+of them while TLS is disabled fails the connector at startup rather than being
silently ignored, so
+a misconfigured truststore never degrades into a plaintext connection.
Likewise, the truststore
+password and type require a truststore path, and the keystore password and
type require a keystore
+path: without one the driver falls back to its default, which they would not
apply to.
+
+If the coordinator certificate is signed by a CA the JVM does not trust,
import it into a
+truststore and point `trino.jdbc.ssl.truststore.path` at it:
+
+```shell
+# Export the coordinator certificate, then import it into a dedicated
truststore.
+openssl s_client -showcerts -connect coordinator.example.com:8443 </dev/null \
+ | openssl x509 -outform PEM > coordinator.pem
+keytool -importcert -noprompt -alias trino-coordinator -file coordinator.pem \
+ -keystore /etc/trino/truststore.jks -storepass YourSecureTruststorePassword
+```
+
+:::caution
+`trino.jdbc.ssl.verification=NONE` disables certificate verification
completely and exposes the
+connection to man-in-the-middle attacks. Use it only for troubleshooting;
import the coordinator
+certificate into a truststore instead.
+:::
+
+The `trino.jdbc.*` properties are used by the coordinator only. They are never
copied into the
+catalogs the connector creates, so the credentials they hold do not reach the
generated
+`CREATE CATALOG` statements or the Trino catalog properties files.
+
+### Mutual TLS and certificate authentication
+
+A coordinator that requires mutual TLS also expects a client certificate. Point
+`trino.jdbc.ssl.keystore.path` at the keystore holding it:
+
+```properties
+trino.jdbc.ssl.keystore.path=/etc/trino/client.p12
+trino.jdbc.ssl.keystore.password=YourSecureKeystorePassword
+```
+
+:::note
+The mutual TLS configuration is provided for completeness and has not been
verified against a
+coordinator that requires client certificates.
+:::
+
+Trino can also be configured with
`http-server.authentication.type=CERTIFICATE`, where the client
+certificate itself is the login and the coordinator derives the username from
the certificate
+subject. The connector does not support that authentication type today: it
always authenticates
+with `trino.jdbc.user`, so a coordinator configured this way rejects the
internal JDBC connection.
+
+### Passing arbitrary JDBC driver properties
+
+Any Trino JDBC driver property that has no dedicated configuration can be
passed through with the
+`trino.jdbc.properties.` prefix. The prefix is stripped and the remainder is
handed to the driver
+verbatim, overriding the value derived from the dedicated `trino.jdbc.*`
configurations:
+
+```properties
+trino.jdbc.properties.KerberosRemoteServiceName=trino
+trino.jdbc.properties.SSLKeyStorePath=/etc/trino/client.p12
+trino.jdbc.properties.SSLKeyStorePassword=YourSecureKeystorePassword
+```
+
+Properties passed through this prefix are handed to the driver without
validation, unlike the
+dedicated `trino.jdbc.*` configurations. An unknown name or an invalid value
therefore surfaces as a
+driver error when the connection is established, not as a configuration error.
+
+See the [Trino JDBC driver
documentation](https://trino.io/docs/current/client/jdbc.html) for the
+full list of supported property names.
+
## Authentication
The Gravitino Trino connector supports authenticating to the Gravitino server
using Simple, Basic, OAuth, and Kerberos authentication. For detailed
authentication configuration, refer to [Trino Connector
Authentication](./authentication.md).
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConfig.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConfig.java
index 1186fcd1c2..817d9f6306 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConfig.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConfig.java
@@ -28,6 +28,7 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
@@ -49,6 +50,8 @@ public class GravitinoConfig {
public static final String TRINO_CATALOG_STORE = "catalog.store";
/** The Trino catalog management. */
public static final String TRINO_CATALOG_MANAGEMENT = "catalog.management";
+ /** The common prefix of all internal Trino JDBC connection configurations.
*/
+ private static final String TRINO_JDBC_CONFIG_PREFIX = "trino.jdbc.";
// Trino config default value
/** The Trino catalog config directory default value. */
@@ -57,6 +60,10 @@ public class GravitinoConfig {
public static final String TRINO_CATALOG_STORE_DEFAULT_VALUE = "file";
/** The Trino catalog management default value. */
public static final String TRINO_CATALOG_MANAGEMENT_DEFAULT_VALUE = "static";
+ /** The default port used when the Trino `discovery.uri` omits it and the
scheme is http. */
+ private static final int HTTP_DEFAULT_PORT = 80;
+ /** The default port used when the Trino `discovery.uri` omits it and the
scheme is https. */
+ private static final int HTTPS_DEFAULT_PORT = 443;
// The Trino configuration of etc/config.properties
/** The Trino configuration. */
@@ -134,6 +141,86 @@ public class GravitinoConfig {
new ConfigEntry(
"trino.jdbc.password", "The JDBC password for connecting to Trino",
"", false);
+ private static final ConfigEntry TRINO_JDBC_SSL_ENABLED =
+ new ConfigEntry(
+ "trino.jdbc.ssl.enabled",
+ "Whether the internal JDBC connection to the Trino coordinator uses
TLS. "
+ + "If not set, it is derived from the scheme of the Trino
`discovery.uri`.",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_TRUSTSTORE_PATH =
+ new ConfigEntry(
+ "trino.jdbc.ssl.truststore.path",
+ "Path of the truststore holding the Trino coordinator certificate. "
+ + "If omitted, the default JVM truststore is used.",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_TRUSTSTORE_PASSWORD =
+ new ConfigEntry(
+ "trino.jdbc.ssl.truststore.password",
+ "Password of the truststore configured by
`trino.jdbc.ssl.truststore.path`",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_TRUSTSTORE_TYPE =
+ new ConfigEntry(
+ "trino.jdbc.ssl.truststore.type",
+ "Type of the truststore, for example JKS or PKCS12. "
+ + "If omitted, the default JVM truststore type is used.",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_KEYSTORE_PATH =
+ new ConfigEntry(
+ "trino.jdbc.ssl.keystore.path",
+ "Path of the keystore holding the client certificate presented to
the Trino coordinator, "
+ + "used by coordinators that require mutual TLS.",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_KEYSTORE_PASSWORD =
+ new ConfigEntry(
+ "trino.jdbc.ssl.keystore.password",
+ "Password of the keystore configured by
`trino.jdbc.ssl.keystore.path`",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_KEYSTORE_TYPE =
+ new ConfigEntry(
+ "trino.jdbc.ssl.keystore.type",
+ "Type of the keystore, for example JKS or PKCS12. "
+ + "If omitted, the default JVM keystore type is used.",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_SSL_VERIFICATION =
+ new ConfigEntry(
+ "trino.jdbc.ssl.verification",
+ "Certificate verification mode of the internal JDBC connection:
FULL, CA or NONE. "
+ + "NONE disables certificate verification and should only be
used for troubleshooting.",
+ "FULL",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_ROLES =
+ new ConfigEntry(
+ "trino.jdbc.roles",
+ "Session roles applied to the internal JDBC connection, for example
`system:sysadmin`. "
+ + "Required by deployments that only allow CREATE CATALOG with a
privileged role.",
+ "",
+ false);
+
+ private static final ConfigEntry TRINO_JDBC_EXTRA_PROPERTIES_PREFIX =
+ new ConfigEntry(
+ "trino.jdbc.properties.",
+ "Prefix for Trino JDBC driver properties. Any property beginning
with this prefix is "
+ + "passed to the driver verbatim with the prefix removed "
+ + "(e.g.,
trino.jdbc.properties.KerberosRemoteServiceName=trino), "
+ + "overriding the properties derived from the dedicated
`trino.jdbc.*` configurations.",
+ "",
+ false);
+
private static final ConfigEntry GRAVITINO_METADATA_REFRESH_INTERVAL_SECOND =
new ConfigEntry(
"gravitino.metadata.refresh-interval-seconds",
@@ -282,15 +369,25 @@ public class GravitinoConfig {
* @return the Trino JDBC URI
*/
public String getTrinoJdbcURI() {
- String uriString = "";
+ URI trinoURI = parseDiscoveryUri();
+ int port = trinoURI.getPort();
+ if (port < 0) {
+ // `discovery.uri` may omit the port, for example a TLS coordinator
behind a load balancer
+ // on the standard HTTPS port. Fall back to the default port of the
scheme.
+ port = isHttpsScheme(trinoURI) ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT;
+ }
+ return String.format("jdbc:trino://%s:%s", trinoURI.getHost(), port);
+ }
+
+ private URI parseDiscoveryUri() {
+ String uriString;
if (config.containsKey(TRINO_DISCOVERY_URI)) {
uriString = config.get(TRINO_DISCOVERY_URI);
} else {
uriString = trinoConfig.getProperty(TRINO_DISCOVERY_URI);
}
try {
- URI trinoURI = new URI(uriString);
- return String.format("jdbc:trino://%s:%s", trinoURI.getHost(),
trinoURI.getPort());
+ return new URI(uriString);
} catch (Exception e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_MISSING_CONFIG,
@@ -298,6 +395,25 @@ public class GravitinoConfig {
}
}
+ private static boolean parseBooleanConfig(String key, String value) {
+ // `Boolean.parseBoolean` maps every unrecognized value to false, which
would silently disable
+ // TLS for a typo such as `yes`, even when the `discovery.uri` scheme
implies it should be on.
+ if ("true".equalsIgnoreCase(value)) {
+ return true;
+ }
+ if ("false".equalsIgnoreCase(value)) {
+ return false;
+ }
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ String.format(
+ "Invalid value for config '%s': expected true or false, got: %s",
key, value));
+ }
+
+ private static boolean isHttpsScheme(URI uri) {
+ return "https".equalsIgnoreCase(uri.getScheme());
+ }
+
/**
* Retrieves the region.
*
@@ -340,6 +456,119 @@ public class GravitinoConfig {
return config.getOrDefault(TRINO_JDBC_PASSWORD.key,
TRINO_JDBC_PASSWORD.defaultValue);
}
+ /**
+ * Returns whether the internal JDBC connection to the Trino coordinator
uses TLS.
+ *
+ * <p>If `trino.jdbc.ssl.enabled` is not set, the value is derived from the
scheme of the Trino
+ * `discovery.uri`, which is `https` on a TLS enabled coordinator.
+ *
+ * @return true if the internal JDBC connection uses TLS
+ */
+ public boolean isTrinoJdbcSslEnabled() {
+ String value = config.get(TRINO_JDBC_SSL_ENABLED.key);
+ if (StringUtils.isNotBlank(value)) {
+ return parseBooleanConfig(TRINO_JDBC_SSL_ENABLED.key, value.trim());
+ }
+ return isHttpsScheme(parseDiscoveryUri());
+ }
+
+ /**
+ * Retrieves the truststore path of the internal JDBC connection.
+ *
+ * @return the truststore path, or an empty string if not configured
+ */
+ public String getTrinoJdbcSslTruststorePath() {
+ return config.getOrDefault(
+ TRINO_JDBC_SSL_TRUSTSTORE_PATH.key,
TRINO_JDBC_SSL_TRUSTSTORE_PATH.defaultValue);
+ }
+
+ /**
+ * Retrieves the truststore password of the internal JDBC connection.
+ *
+ * @return the truststore password, or an empty string if not configured
+ */
+ public String getTrinoJdbcSslTruststorePassword() {
+ return config.getOrDefault(
+ TRINO_JDBC_SSL_TRUSTSTORE_PASSWORD.key,
TRINO_JDBC_SSL_TRUSTSTORE_PASSWORD.defaultValue);
+ }
+
+ /**
+ * Retrieves the truststore type of the internal JDBC connection.
+ *
+ * @return the truststore type, or an empty string if not configured
+ */
+ public String getTrinoJdbcSslTruststoreType() {
+ return config.getOrDefault(
+ TRINO_JDBC_SSL_TRUSTSTORE_TYPE.key,
TRINO_JDBC_SSL_TRUSTSTORE_TYPE.defaultValue);
+ }
+
+ /**
+ * Retrieves the keystore path of the internal JDBC connection.
+ *
+ * @return the keystore path, or an empty string if not configured
+ */
+ public String getTrinoJdbcSslKeystorePath() {
+ return config.getOrDefault(
+ TRINO_JDBC_SSL_KEYSTORE_PATH.key,
TRINO_JDBC_SSL_KEYSTORE_PATH.defaultValue);
+ }
+
+ /**
+ * Retrieves the keystore password of the internal JDBC connection.
+ *
+ * @return the keystore password, or an empty string if not configured
+ */
+ public String getTrinoJdbcSslKeystorePassword() {
+ return config.getOrDefault(
+ TRINO_JDBC_SSL_KEYSTORE_PASSWORD.key,
TRINO_JDBC_SSL_KEYSTORE_PASSWORD.defaultValue);
+ }
+
+ /**
+ * Retrieves the keystore type of the internal JDBC connection.
+ *
+ * @return the keystore type, or an empty string if not configured
+ */
+ public String getTrinoJdbcSslKeystoreType() {
+ return config.getOrDefault(
+ TRINO_JDBC_SSL_KEYSTORE_TYPE.key,
TRINO_JDBC_SSL_KEYSTORE_TYPE.defaultValue);
+ }
+
+ /**
+ * Retrieves the certificate verification mode of the internal JDBC
connection.
+ *
+ * @return the verification mode, one of FULL, CA or NONE
+ */
+ public String getTrinoJdbcSslVerification() {
+ String value = config.get(TRINO_JDBC_SSL_VERIFICATION.key);
+ if (StringUtils.isBlank(value)) {
+ return TRINO_JDBC_SSL_VERIFICATION.defaultValue;
+ }
+ return value.trim().toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Retrieves the session roles applied to the internal JDBC connection.
+ *
+ * @return the session roles, or an empty string if not configured
+ */
+ public String getTrinoJdbcRoles() {
+ return config.getOrDefault(TRINO_JDBC_ROLES.key,
TRINO_JDBC_ROLES.defaultValue);
+ }
+
+ /**
+ * Retrieves the Trino JDBC driver properties configured with the
`trino.jdbc.properties.` prefix.
+ *
+ * @return a map of driver property names, with the prefix removed, to their
values
+ */
+ public Map<String, String> getTrinoJdbcExtraProperties() {
+ return config.entrySet().stream()
+ .filter(entry ->
entry.getKey().startsWith(TRINO_JDBC_EXTRA_PROPERTIES_PREFIX.key))
+ .filter(entry -> entry.getKey().length() >
TRINO_JDBC_EXTRA_PROPERTIES_PREFIX.key.length())
+ .collect(
+ Collectors.toMap(
+ entry ->
entry.getKey().substring(TRINO_JDBC_EXTRA_PROPERTIES_PREFIX.key.length()),
+ Map.Entry::getValue));
+ }
+
/**
* Retrieves the catalog connector factory class name.
*
@@ -359,6 +588,13 @@ public class GravitinoConfig {
public String toCatalogConfig() {
List<String> stringList = new ArrayList<>();
for (Map.Entry<String, ConfigEntry> entry : CONFIG_DEFINITIONS.entrySet())
{
+ // The `trino.jdbc.*` configurations are only used by the coordinator to
connect back to
+ // Trino. They must not be propagated to the dynamic catalogs, otherwise
credentials such as
+ // the JDBC password and the truststore password would end up in the
generated CREATE CATALOG
+ // statement, which is logged and persisted into the Trino catalog
properties files.
+ if (entry.getKey().startsWith(TRINO_JDBC_CONFIG_PREFIX)) {
+ continue;
+ }
String value = config.get(entry.getKey());
if (value != null) {
stringList.add(String.format("\"%s\"='%s'", entry.getKey(), value));
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnectorFactory.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnectorFactory.java
index 5315342713..6308b7d1bc 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnectorFactory.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnectorFactory.java
@@ -64,6 +64,7 @@ public class GravitinoConnectorFactory implements
ConnectorFactory {
private GravitinoSystemTableFactory gravitinoSystemTableFactory;
private CatalogConnectorManager catalogConnectorManager;
+ private boolean catalogConnectorManagerStartTriggered = false;
private GravitinoAdminClient client;
private int trinoVersion;
@@ -87,6 +88,16 @@ public class GravitinoConnectorFactory implements
ConnectorFactory {
return catalogConnectorManager;
}
+ /**
+ * Returns whether starting the catalog connector manager has been triggered.
+ *
+ * @return true if starting the catalog connector manager has been triggered
+ */
+ @VisibleForTesting
+ public boolean isCatalogConnectorManagerStartTriggered() {
+ return catalogConnectorManagerStartTriggered;
+ }
+
/**
* This function call by Trino creates a connector. It creates
GravitinoSystemConnector at first.
* Another time's it get GravitinoConnector by CatalogConnectorManager
@@ -104,9 +115,14 @@ public class GravitinoConnectorFactory implements
ConnectorFactory {
GravitinoConfig config = new GravitinoConfig(requiredConfig);
synchronized (this) {
+ // Keep the version check out of the try below so that it keeps
reporting its own error code
+ // instead of being wrapped as a generic initialization failure.
if (catalogConnectorManager == null) {
checkTrinoSpiVersion(trinoConnectorContext, config);
- try {
+ }
+
+ try {
+ if (catalogConnectorManager == null) {
CatalogRegister catalogRegister = new CatalogRegister();
CatalogConnectorFactory catalogConnectorFactory =
createCatalogConnectorFactory(config);
@@ -115,16 +131,31 @@ public class GravitinoConnectorFactory implements
ConnectorFactory {
catalogRegister, catalogConnectorFactory,
this::getTrinoCatalogName);
catalogConnectorManager.config(config, client);
- if (isCoordinator(trinoConnectorContext)) {
- catalogConnectorManager.start();
- }
-
gravitinoSystemTableFactory = new
GravitinoSystemTableFactory(catalogConnectorManager);
- } catch (Exception e) {
- String message = "Initialization of the GravitinoConnector failed "
+ e.getMessage();
- LOG.error(message);
- throw new TrinoException(GRAVITINO_RUNTIME_ERROR, message, e);
}
+
+ // The `trino.jdbc.*` settings that CatalogRegister needs to connect
back to the
+ // coordinator are deliberately not propagated to the dynamic
catalogs, so they are only
+ // present in the configuration of the static connector. Trino does
not guarantee that the
+ // static catalog is loaded before the catalogs Gravitino created,
therefore the manager is
+ // started from the static connector only, re-applying its
configuration in case a dynamic
+ // connector was created first.
+ if (!catalogConnectorManagerStartTriggered
+ && !config.isDynamicConnector()
+ && isCoordinator(trinoConnectorContext)) {
+ // Triggered before start() on purpose: everything that makes it
fail is a
+ // configuration error, and retrying on the next create() would only
open another
+ // connection.
+ catalogConnectorManagerStartTriggered = true;
+ // Only the configuration is re-applied here: rebuilding the
Gravitino client would leak
+ // the one a dynamic connector may have already built.
+ catalogConnectorManager.updateConfig(config);
+ catalogConnectorManager.start();
+ }
+ } catch (Exception e) {
+ String message = "Initialization of the GravitinoConnector failed " +
e.getMessage();
+ LOG.error(message);
+ throw new TrinoException(GRAVITINO_RUNTIME_ERROR, message, e);
}
}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
index 7539a0b439..34872d2e2f 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
@@ -114,8 +114,7 @@ public class CatalogConnectorManager {
* @param client the Gravitino admin client
*/
public void config(GravitinoConfig config, GravitinoAdminClient client) {
- Preconditions.checkArgument(config != null, "config must not be null");
- this.config = config;
+ updateConfig(config);
if (client == null) {
String authType =
config.getClientConfig().getOrDefault(GravitinoAuthProvider.AUTH_TYPE_KEY,
"none");
@@ -142,6 +141,20 @@ public class CatalogConnectorManager {
} else {
this.gravitinoClient = client;
}
+ }
+
+ /**
+ * Updates the Gravitino configuration, leaving the Gravitino client
untouched.
+ *
+ * <p>Used to re-apply the configuration of the static connector when a
dynamic connector created
+ * the manager first, so that the catalog register is started with the
`trino.jdbc.*` settings
+ * that are only present in the static configuration.
+ *
+ * @param config the Gravitino configuration
+ */
+ public void updateConfig(GravitinoConfig config) {
+ Preconditions.checkArgument(config != null, "config must not be null");
+ this.config = config;
this.metadataUpdateIntervalSecond =
Integer.parseInt(config.getMetadataRefreshIntervalSecond());
this.targetMetalake = config.getMetalake();
}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogRegister.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogRegister.java
index 09bf1cd658..faca9c2499 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogRegister.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogRegister.java
@@ -21,17 +21,21 @@ package org.apache.gravitino.trino.connector.catalog;
import static
org.apache.gravitino.trino.connector.GravitinoConfig.GRAVITINO_DYNAMIC_CONNECTOR;
import static
org.apache.gravitino.trino.connector.GravitinoConfig.GRAVITINO_DYNAMIC_CONNECTOR_CATALOG_CONFIG;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
import io.trino.jdbc.TrinoDriver;
import io.trino.spi.TrinoException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.Map;
import java.util.Properties;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.trino.connector.GravitinoConfig;
import org.apache.gravitino.trino.connector.GravitinoErrorCode;
import org.apache.gravitino.trino.connector.metadata.GravitinoCatalog;
@@ -49,6 +53,12 @@ public class CatalogRegister {
private static final int EXECUTE_QUERY_MAX_RETRIES = 6;
private static final int EXECUTE_QUERY_BACKOFF_TIME_SECOND = 5;
+ private static final String SSL_VERIFICATION_FULL = "FULL";
+ private static final String SSL_VERIFICATION_CA = "CA";
+ private static final String SSL_VERIFICATION_NONE = "NONE";
+ private static final Set<String> SSL_VERIFICATION_MODES =
+ ImmutableSet.of(SSL_VERIFICATION_FULL, SSL_VERIFICATION_CA,
SSL_VERIFICATION_NONE);
+
private Connection connection;
private boolean isStarted = false;
private String catalogStoreDirectory;
@@ -80,17 +90,16 @@ public class CatalogRegister {
this.config = config;
TrinoDriver driver = new TrinoDriver();
- DriverManager.registerDriver(driver);
-
- Properties properties = new Properties();
- properties.put("user", config.getTrinoUser());
- properties.put("password", config.getTrinoPassword());
+ Properties properties = buildJdbcProperties(config);
+ String jdbcUri = config.getTrinoJdbcURI();
try {
- connection = driver.connect(config.getTrinoJdbcURI(), properties);
+ connection = driver.connect(jdbcUri, properties);
} catch (SQLException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_RUNTIME_ERROR,
- "Failed to initialize the Trino connection.",
+ String.format(
+ "Failed to initialize the Trino connection to %s (TLS %s).",
+ jdbcUri, config.isTrinoJdbcSslEnabled() ? "enabled" :
"disabled"),
e);
}
@@ -104,6 +113,195 @@ public class CatalogRegister {
}
}
+ /**
+ * Builds the JDBC properties used by the internal connection to the Trino
coordinator.
+ *
+ * <p>The properties derived from the dedicated {@code trino.jdbc.*}
configurations are applied
+ * first, then the raw driver properties configured with the {@code
trino.jdbc.properties.} prefix
+ * are applied on top of them, so that any driver property can be overridden.
+ *
+ * @param config the Gravitino configuration
+ * @return the JDBC properties
+ */
+ @VisibleForTesting
+ static Properties buildJdbcProperties(GravitinoConfig config) {
+ boolean sslEnabled = config.isTrinoJdbcSslEnabled();
+ String verification = config.getTrinoJdbcSslVerification();
+ String truststorePath = config.getTrinoJdbcSslTruststorePath();
+ String truststorePassword = config.getTrinoJdbcSslTruststorePassword();
+ String truststoreType = config.getTrinoJdbcSslTruststoreType();
+ String keystorePath = config.getTrinoJdbcSslKeystorePath();
+ String keystorePassword = config.getTrinoJdbcSslKeystorePassword();
+ String keystoreType = config.getTrinoJdbcSslKeystoreType();
+ String roles = config.getTrinoJdbcRoles();
+
+ validateSslConfig(
+ sslEnabled,
+ verification,
+ truststorePath,
+ truststorePassword,
+ truststoreType,
+ keystorePath,
+ keystorePassword,
+ keystoreType);
+
+ Properties properties = new Properties();
+ properties.put("user", config.getTrinoUser());
+ String password = config.getTrinoPassword();
+ if (StringUtils.isNotEmpty(password)) {
+ properties.put("password", password);
+ }
+
+ if (sslEnabled) {
+ properties.put("SSL", "true");
+ properties.put("SSLVerification", verification);
+ if (StringUtils.isNotBlank(truststorePath)) {
+ properties.put("SSLTrustStorePath", truststorePath);
+ }
+ if (StringUtils.isNotEmpty(truststorePassword)) {
+ properties.put("SSLTrustStorePassword", truststorePassword);
+ }
+ if (StringUtils.isNotBlank(truststoreType)) {
+ properties.put("SSLTrustStoreType", truststoreType);
+ }
+ if (StringUtils.isNotBlank(keystorePath)) {
+ properties.put("SSLKeyStorePath", keystorePath);
+ }
+ if (StringUtils.isNotEmpty(keystorePassword)) {
+ properties.put("SSLKeyStorePassword", keystorePassword);
+ }
+ if (StringUtils.isNotBlank(keystoreType)) {
+ properties.put("SSLKeyStoreType", keystoreType);
+ }
+ }
+
+ if (StringUtils.isNotBlank(roles)) {
+ properties.put("roles", roles);
+ }
+
+ Map<String, String> extraProperties = config.getTrinoJdbcExtraProperties();
+ if (!extraProperties.isEmpty()) {
+ // Log the names only, the values may contain credentials.
+ LOG.debug("Applying extra Trino JDBC properties: {}",
extraProperties.keySet());
+ extraProperties.keySet().stream()
+ .filter(key -> key.startsWith("SSL") && properties.containsKey(key))
+ .forEach(
+ key ->
+ LOG.warn(
+ "Extra Trino JDBC property '{}' overrides the TLS
setting derived from the "
+ + "dedicated configuration and is applied without
validation",
+ key));
+ properties.putAll(extraProperties);
+ }
+ return properties;
+ }
+
+ private static void validateSslConfig(
+ boolean sslEnabled,
+ String verification,
+ String truststorePath,
+ String truststorePassword,
+ String truststoreType,
+ String keystorePath,
+ String keystorePassword,
+ String keystoreType) {
+ if (!SSL_VERIFICATION_MODES.contains(verification)) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ String.format(
+ "Invalid value for config 'trino.jdbc.ssl.verification':
expected one of %s, got: %s",
+ SSL_VERIFICATION_MODES, verification));
+ }
+
+ if (!sslEnabled) {
+ if (!SSL_VERIFICATION_FULL.equals(verification)) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ "Config 'trino.jdbc.ssl.verification' requires TLS to be enabled
either by an HTTPS "
+ + "'discovery.uri' or by 'trino.jdbc.ssl.enabled=true'");
+ }
+ checkRequiresSslEnabled("trino.jdbc.ssl.truststore.path",
truststorePath);
+ checkRequiresSslEnabled("trino.jdbc.ssl.truststore.password",
truststorePassword);
+ checkRequiresSslEnabled("trino.jdbc.ssl.truststore.type",
truststoreType);
+ checkRequiresSslEnabled("trino.jdbc.ssl.keystore.path", keystorePath);
+ checkRequiresSslEnabled("trino.jdbc.ssl.keystore.password",
keystorePassword);
+ checkRequiresSslEnabled("trino.jdbc.ssl.keystore.type", keystoreType);
+ return;
+ }
+
+ validateKeystoreConfig(verification, keystorePath, keystorePassword,
keystoreType);
+
+ if (StringUtils.isBlank(truststorePath)) {
+ // The driver falls back to the default JVM truststore, which the
password and the type of a
+ // truststore that was never configured have nothing to apply to.
+ checkRequires(
+ "trino.jdbc.ssl.truststore.password",
+ truststorePassword,
+ "trino.jdbc.ssl.truststore.path");
+ checkRequires(
+ "trino.jdbc.ssl.truststore.type", truststoreType,
"trino.jdbc.ssl.truststore.path");
+ return;
+ }
+
+ if (SSL_VERIFICATION_NONE.equals(verification)) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ "Config 'trino.jdbc.ssl.truststore.path' cannot be used with "
+ + "'trino.jdbc.ssl.verification' = NONE");
+ }
+ if (!Files.exists(Path.of(truststorePath))) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_MISSING_CONFIG,
+ String.format(
+ "The truststore file configured by
'trino.jdbc.ssl.truststore.path' does not exist: %s",
+ truststorePath));
+ }
+ }
+
+ private static void validateKeystoreConfig(
+ String verification, String keystorePath, String keystorePassword,
String keystoreType) {
+ if (StringUtils.isBlank(keystorePath)) {
+ checkRequires(
+ "trino.jdbc.ssl.keystore.password", keystorePassword,
"trino.jdbc.ssl.keystore.path");
+ checkRequires("trino.jdbc.ssl.keystore.type", keystoreType,
"trino.jdbc.ssl.keystore.path");
+ return;
+ }
+ if (SSL_VERIFICATION_NONE.equals(verification)) {
+ // The driver rejects the keystore properties in this combination, so
fail with a config
+ // error here rather than letting it surface as a connection failure.
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ "Config 'trino.jdbc.ssl.keystore.path' cannot be used with "
+ + "'trino.jdbc.ssl.verification' = NONE");
+ }
+ if (!Files.exists(Path.of(keystorePath))) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_MISSING_CONFIG,
+ String.format(
+ "The keystore file configured by 'trino.jdbc.ssl.keystore.path'
does not exist: %s",
+ keystorePath));
+ }
+ }
+
+ private static void checkRequires(String key, String value, String
requiredKey) {
+ if (StringUtils.isNotEmpty(value)) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ String.format("Config '%s' requires '%s' to be set", key,
requiredKey));
+ }
+ }
+
+ private static void checkRequiresSslEnabled(String key, String value) {
+ if (StringUtils.isNotEmpty(value)) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
+ String.format(
+ "Config '%s' requires TLS to be enabled either by an HTTPS
'discovery.uri' or by "
+ + "'trino.jdbc.ssl.enabled=true'",
+ key));
+ }
+ }
+
private String generateCreateCatalogCommand(String name, GravitinoCatalog
gravitinoCatalog)
throws Exception {
return String.format(
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConfig.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConfig.java
index 8aa65b4b69..2d486b52c0 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConfig.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConfig.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.trino.connector;
import static
org.apache.gravitino.trino.connector.GravitinoErrorCode.GRAVITINO_MISSING_CONFIG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -168,6 +169,137 @@ public class TestGravitinoConfig {
assertTrue(catalogConfig.contains("\"gravitino.user\"='admin'"));
}
+ @Test
+ public void testTrinoJdbcConfigDefaults() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of("gravitino.metalake", "user_001", "discovery.uri",
"http://host:8080"));
+
+ assertEquals("admin", config.getTrinoUser());
+ assertEquals("", config.getTrinoPassword());
+ assertFalse(config.isTrinoJdbcSslEnabled());
+ assertEquals("FULL", config.getTrinoJdbcSslVerification());
+ assertEquals("", config.getTrinoJdbcSslTruststorePath());
+ assertEquals("", config.getTrinoJdbcSslTruststorePassword());
+ assertEquals("", config.getTrinoJdbcSslTruststoreType());
+ assertEquals("", config.getTrinoJdbcRoles());
+ assertTrue(config.getTrinoJdbcExtraProperties().isEmpty());
+ }
+
+ @Test
+ public void testTrinoJdbcSslEnabledDerivedFromDiscoveryUri() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of(
+ "gravitino.metalake", "user_001", "discovery.uri",
"https://host:8443"));
+
+ assertTrue(config.isTrinoJdbcSslEnabled());
+ assertEquals("jdbc:trino://host:8443", config.getTrinoJdbcURI());
+ }
+
+ @Test
+ public void testTrinoJdbcExtraProperties() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of(
+ "gravitino.metalake",
+ "user_001",
+ "discovery.uri",
+ "http://host:8080",
+ "trino.jdbc.properties.KerberosRemoteServiceName",
+ "trino",
+ "trino.jdbc.properties.SSLKeyStorePath",
+ "/etc/trino/client.p12"));
+
+ Map<String, String> extraProperties = config.getTrinoJdbcExtraProperties();
+ assertEquals(2, extraProperties.size());
+ assertEquals("trino", extraProperties.get("KerberosRemoteServiceName"));
+ assertEquals("/etc/trino/client.p12",
extraProperties.get("SSLKeyStorePath"));
+ }
+
+ @Test
+ public void testToCatalogConfigExcludesTrinoJdbcProperties() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of(
+ "gravitino.metalake",
+ "user_001",
+ "trino.jdbc.user",
+ "admin",
+ "trino.jdbc.password",
+ "jdbc-secret",
+ "trino.jdbc.ssl.truststore.password",
+ "truststore-secret",
+ "trino.jdbc.properties.SSLKeyStorePassword",
+ "keystore-secret"));
+
+ // The internal JDBC connection settings are coordinator only. They must
never reach the
+ // generated CREATE CATALOG statement, which is logged and persisted to
the catalog files.
+ String catalogConfig = config.toCatalogConfig();
+ assertFalse(catalogConfig.contains("trino.jdbc."));
+ assertFalse(catalogConfig.contains("secret"));
+ assertTrue(catalogConfig.contains("\"gravitino.metalake\"='user_001'"));
+ }
+
+ @Test
+ public void testTrinoJdbcUriUsesSchemeDefaultPort() {
+ GravitinoConfig httpsConfig =
+ new GravitinoConfig(
+ ImmutableMap.of("gravitino.metalake", "user_001", "discovery.uri",
"https://host"));
+ assertEquals("jdbc:trino://host:443", httpsConfig.getTrinoJdbcURI());
+
+ GravitinoConfig httpConfig =
+ new GravitinoConfig(
+ ImmutableMap.of("gravitino.metalake", "user_001", "discovery.uri",
"http://host"));
+ assertEquals("jdbc:trino://host:80", httpConfig.getTrinoJdbcURI());
+ }
+
+ @Test
+ public void testBlankSslVerificationFallsBackToDefault() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of(
+ "gravitino.metalake",
+ "user_001",
+ "discovery.uri",
+ "http://host:8080",
+ "trino.jdbc.ssl.verification",
+ " "));
+
+ assertEquals("FULL", config.getTrinoJdbcSslVerification());
+ }
+
+ @Test
+ public void testInvalidSslEnabledIsRejected() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of(
+ "gravitino.metalake",
+ "user_001",
+ // An HTTPS discovery.uri would have derived true, so a typo
must not silently
+ // fall back to false.
+ "discovery.uri",
+ "https://host:8443",
+ "trino.jdbc.ssl.enabled",
+ "yes"));
+
+ TrinoException e = assertThrows(TrinoException.class,
config::isTrinoJdbcSslEnabled);
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.enabled"));
+ assertTrue(e.getMessage().contains("expected true or false"));
+ }
+
+ @Test
+ public void testSslEnabledAcceptsMixedCase() {
+ GravitinoConfig config =
+ new GravitinoConfig(
+ ImmutableMap.of(
+ "gravitino.metalake", "user_001",
+ "discovery.uri", "http://host:8080",
+ "trino.jdbc.ssl.enabled", " TRUE "));
+
+ assertTrue(config.isTrinoJdbcSslEnabled());
+ }
+
private static boolean skipCatalog(String catalogName, GravitinoConfig
config) {
for (Pattern pattern : config.getSkipCatalogPatterns()) {
if (pattern.matcher(catalogName).matches()) {
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorFactoryStart.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorFactoryStart.java
new file mode 100644
index 0000000000..b104b4d509
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorFactoryStart.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.trino.connector;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import io.trino.spi.connector.ConnectorContext;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.gravitino.client.GravitinoAdminClient;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Verifies that the catalog connector manager is only started from the static
connector.
+ *
+ * <p>The `trino.jdbc.*` settings are not propagated to the dynamic catalogs,
so starting the
+ * manager from a dynamic connector would make the internal JDBC connection
lose its credentials and
+ * TLS settings.
+ */
+public class TestGravitinoConnectorFactoryStart {
+
+ // Nothing listens on this port, but the Trino driver connects lazily, so
the register still
+ // initializes successfully.
+ private static final String COORDINATOR_URI = "http://127.0.0.1:1";
+
+ @TempDir private static Path catalogStoreDir;
+
+ private static Map<String, String> staticConfig() {
+ Map<String, String> config = new HashMap<>();
+ config.put("gravitino.metalake", "test");
+ config.put("discovery.uri", COORDINATOR_URI);
+ config.put("catalog.config-dir", catalogStoreDir.toString());
+ config.put("trino.jdbc.user", "gravitino");
+ return config;
+ }
+
+ private static Map<String, String> dynamicConfig() {
+ Map<String, String> config = new HashMap<>();
+ config.put("gravitino.metalake", "test");
+ // A dynamic catalog config carries neither `discovery.uri` nor any
`trino.jdbc.*` setting.
+ config.put(GravitinoConfig.GRAVITINO_DYNAMIC_CONNECTOR, "true");
+ config.put(GravitinoConfig.GRAVITINO_DYNAMIC_CONNECTOR_CATALOG_CONFIG,
"{}");
+ return config;
+ }
+
+ private static ConnectorContext mockContext() {
+ ConnectorContext context = mock(ConnectorContext.class);
+ when(context.getSpiVersion()).thenReturn("478");
+ return context;
+ }
+
+ /** A factory that always reports itself as running on the coordinator. */
+ private static class CoordinatorFactory extends GravitinoConnectorFactory {
+ CoordinatorFactory(GravitinoAdminClient client) {
+ super(client);
+ }
+
+ @Override
+ protected boolean isCoordinator(ConnectorContext connectorContext) {
+ return true;
+ }
+ }
+
+ private static CoordinatorFactory newFactory() {
+ return new CoordinatorFactory(mock(GravitinoAdminClient.class));
+ }
+
+ @Test
+ public void testStaticConnectorStartsTheManager() {
+ CoordinatorFactory factory = newFactory();
+
+ assertNotNull(factory.create("gravitino", staticConfig(), mockContext()));
+ assertTrue(factory.isCatalogConnectorManagerStartTriggered());
+ }
+
+ @Test
+ public void testDynamicConnectorDoesNotStartTheManager() {
+ CoordinatorFactory factory = newFactory();
+
+ Exception e =
+ assertThrows(
+ Exception.class, () -> factory.create("catalog", dynamicConfig(),
mockContext()));
+
+ // It fails later while building the dynamic catalog, never by connecting
back to Trino.
+ assertFalse(
+ e.getMessage().contains("jdbc:trino://"),
+ "The manager must not be started from a dynamic connector, got: " +
e.getMessage());
+ assertNotNull(factory.getCatalogConnectorManager());
+ assertFalse(factory.isCatalogConnectorManagerStartTriggered());
+ }
+
+ @Test
+ public void testStaticConnectorStartsTheManagerAfterADynamicOne() {
+ CoordinatorFactory factory = newFactory();
+
+ // The coordinator restarted and Trino loaded a Gravitino-created catalog
first.
+ assertThrows(Exception.class, () -> factory.create("catalog",
dynamicConfig(), mockContext()));
+
+ // The static catalog is loaded afterwards and must start the manager with
its own config.
+ // Had the dynamic config been used instead, `discovery.uri` would have
been missing and the
+ // register would have failed to build the JDBC URI.
+ assertNotNull(factory.create("gravitino", staticConfig(), mockContext()));
+ assertTrue(factory.isCatalogConnectorManagerStartTriggered());
+ }
+
+ @Test
+ public void testStartIsAttemptedOnlyOnce() {
+ CoordinatorFactory factory = newFactory();
+
+ Map<String, String> brokenConfig = staticConfig();
+ brokenConfig.put("catalog.config-dir", "/not/exists/catalog");
+ assertThrows(Exception.class, () -> factory.create("gravitino",
brokenConfig, mockContext()));
+
+ // Everything that makes start() fail is a configuration error, so the
next create() must not
+ // try again: a second init() would open another connection and abandon
the first one.
+ assertNotNull(factory.create("gravitino", brokenConfig, mockContext()));
+ }
+
+ @Test
+ public void testDynamicConfigCarriesNoJdbcSettings() {
+ GravitinoConfig config = new
GravitinoConfig(ImmutableMap.copyOf(staticConfig()));
+
+ // What a dynamic catalog would receive: no `trino.jdbc.*`, no
`discovery.uri`.
+ String catalogConfig = config.toCatalogConfig();
+ assertFalse(catalogConfig.contains("trino.jdbc."));
+ assertFalse(catalogConfig.contains("discovery.uri"));
+ }
+}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogRegister.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogRegister.java
new file mode 100644
index 0000000000..2effd98843
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogRegister.java
@@ -0,0 +1,380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.trino.connector.catalog;
+
+import static
org.apache.gravitino.trino.connector.GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT;
+import static
org.apache.gravitino.trino.connector.GravitinoErrorCode.GRAVITINO_MISSING_CONFIG;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.trino.spi.TrinoException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.gravitino.trino.connector.GravitinoConfig;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class TestCatalogRegister {
+
+ @TempDir private static Path tempDir;
+
+ private static GravitinoConfig config(Map<String, String> extraConfig) {
+ Map<String, String> configMap = new HashMap<>();
+ configMap.put("gravitino.metalake", "test");
+ configMap.put("discovery.uri", "http://localhost:8080");
+ configMap.putAll(extraConfig);
+ return new GravitinoConfig(configMap);
+ }
+
+ private static Path createStoreFile() throws IOException {
+ return Files.createTempFile(tempDir, "store", ".jks");
+ }
+
+ @Test
+ public void testDefaultProperties() {
+ Properties properties =
CatalogRegister.buildJdbcProperties(config(Map.of()));
+
+ assertEquals("admin", properties.get("user"));
+ // An empty password must not be sent to the driver.
+ assertNull(properties.get("password"));
+ assertNull(properties.get("SSL"));
+ assertNull(properties.get("roles"));
+ }
+
+ @Test
+ public void testPasswordIsSetWhenNotEmpty() {
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(
+ config(Map.of("trino.jdbc.user", "gravitino",
"trino.jdbc.password", "secret")));
+
+ assertEquals("gravitino", properties.get("user"));
+ assertEquals("secret", properties.get("password"));
+ }
+
+ @Test
+ public void testHttpsDiscoveryUriWithTruststore() throws IOException {
+ String truststore = createStoreFile().toString();
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "discovery.uri", "https://localhost:8443",
+ "trino.jdbc.ssl.truststore.path", truststore,
+ "trino.jdbc.ssl.truststore.password", "truststore-secret",
+ "trino.jdbc.ssl.truststore.type", "PKCS12")));
+
+ assertEquals("true", properties.get("SSL"));
+ assertEquals("FULL", properties.get("SSLVerification"));
+ assertEquals(truststore, properties.get("SSLTrustStorePath"));
+ assertEquals("truststore-secret", properties.get("SSLTrustStorePassword"));
+ assertEquals("PKCS12", properties.get("SSLTrustStoreType"));
+ }
+
+ @Test
+ public void testSslEnabledDerivedFromDiscoveryUri() {
+ GravitinoConfig config = config(Map.of("discovery.uri",
"https://localhost:8443"));
+ assertTrue(config.isTrinoJdbcSslEnabled());
+
+ Properties properties = CatalogRegister.buildJdbcProperties(config);
+ assertEquals("true", properties.get("SSL"));
+ assertEquals("FULL", properties.get("SSLVerification"));
+ // Without a configured truststore the driver falls back to the default
JVM truststore.
+ assertNull(properties.get("SSLTrustStorePath"));
+ }
+
+ @Test
+ public void testExplicitSslEnabledOverridesDiscoveryUri() {
+ GravitinoConfig config =
+ config(
+ Map.of("discovery.uri", "https://localhost:8443",
"trino.jdbc.ssl.enabled", "false"));
+ assertFalse(config.isTrinoJdbcSslEnabled());
+ assertNull(CatalogRegister.buildJdbcProperties(config).get("SSL"));
+ }
+
+ @Test
+ public void testSslEnabledIgnoresSurroundingWhitespace() {
+ GravitinoConfig config = config(Map.of("trino.jdbc.ssl.enabled", " true
"));
+ assertTrue(config.isTrinoJdbcSslEnabled());
+ assertEquals("true",
CatalogRegister.buildJdbcProperties(config).get("SSL"));
+ }
+
+ @Test
+ public void testSslVerificationIsNormalized() {
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of("trino.jdbc.ssl.enabled", "true",
"trino.jdbc.ssl.verification", " none ")));
+
+ assertEquals("NONE", properties.get("SSLVerification"));
+ }
+
+ @Test
+ public void testRoles() {
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(config(Map.of("trino.jdbc.roles",
"system:sysadmin")));
+
+ assertEquals("system:sysadmin", properties.get("roles"));
+ }
+
+ @Test
+ public void testExtraPropertiesArePassedThroughAndOverride() {
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.properties.KerberosRemoteServiceName", "trino",
+ "trino.jdbc.properties.SSLVerification", "CA")));
+
+ assertEquals("trino", properties.get("KerberosRemoteServiceName"));
+ // The escape hatch wins over the value derived from the dedicated
configuration.
+ assertEquals("CA", properties.get("SSLVerification"));
+ }
+
+ @Test
+ public void testHttpsDiscoveryUriWithKeystore() throws IOException {
+ String keystore = createStoreFile().toString();
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "discovery.uri", "https://localhost:8443",
+ "trino.jdbc.ssl.keystore.path", keystore,
+ "trino.jdbc.ssl.keystore.password", "keystore-secret",
+ "trino.jdbc.ssl.keystore.type", "PKCS12")));
+
+ assertEquals(keystore, properties.get("SSLKeyStorePath"));
+ assertEquals("keystore-secret", properties.get("SSLKeyStorePassword"));
+ assertEquals("PKCS12", properties.get("SSLKeyStoreType"));
+ }
+
+ @Test
+ public void testKeystoreWithoutSslEnabled() throws IOException {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(Map.of("trino.jdbc.ssl.keystore.path",
createStoreFile().toString()))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.keystore.path"));
+ }
+
+ @Test
+ public void testKeystorePasswordWithoutKeystorePath() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.keystore.password",
"keystore-secret"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.keystore.path"));
+ }
+
+ @Test
+ public void testKeystoreFileNotFound() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.keystore.path",
"/not/exists/keystore.p12"))));
+
+ assertEquals(GRAVITINO_MISSING_CONFIG.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("does not exist"));
+ }
+
+ @Test
+ public void testKeystoreWithVerificationNone() throws IOException {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.verification", "NONE",
+ "trino.jdbc.ssl.keystore.path",
createStoreFile().toString()))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("NONE"));
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.keystore.path"));
+ }
+
+ @Test
+ public void testInvalidSslVerification() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.verification", "PARTIAL"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.verification"));
+ }
+
+ @Test
+ public void testSslVerificationWithoutSslEnabled() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(Map.of("trino.jdbc.ssl.verification", "NONE"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.enabled"));
+ }
+
+ @Test
+ public void testTruststoreWithoutSslEnabled() throws IOException {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of("trino.jdbc.ssl.truststore.path",
createStoreFile().toString()))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.truststore.path"));
+ }
+
+ @Test
+ public void testTruststorePasswordWithoutSslEnabled() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(Map.of("trino.jdbc.ssl.truststore.password",
"truststore-secret"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.truststore.password"));
+ }
+
+ @Test
+ public void testTruststoreTypeWithoutSslEnabled() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(Map.of("trino.jdbc.ssl.truststore.type",
"PKCS12"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.truststore.type"));
+ }
+
+ @Test
+ public void testBlankSslVerificationFallsBackToDefault() {
+ Properties properties =
+ CatalogRegister.buildJdbcProperties(
+ config(Map.of("trino.jdbc.ssl.enabled", "true",
"trino.jdbc.ssl.verification", "")));
+
+ assertEquals("FULL", properties.get("SSLVerification"));
+ }
+
+ @Test
+ public void testTruststorePasswordWithoutTruststorePath() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.truststore.password",
"truststore-secret"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.truststore.path"));
+ }
+
+ @Test
+ public void testTruststoreTypeWithoutTruststorePath() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.truststore.type", "PKCS12"))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.truststore.path"));
+ }
+
+ @Test
+ public void testTruststoreWithVerificationNone() throws IOException {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.verification", "NONE",
+ "trino.jdbc.ssl.truststore.path",
createStoreFile().toString()))));
+
+ assertEquals(GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("NONE"));
+ }
+
+ @Test
+ public void testTruststoreFileNotFound() {
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ CatalogRegister.buildJdbcProperties(
+ config(
+ Map.of(
+ "trino.jdbc.ssl.enabled", "true",
+ "trino.jdbc.ssl.truststore.path",
"/not/exists/truststore.jks"))));
+
+ assertEquals(GRAVITINO_MISSING_CONFIG.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("does not exist"));
+ assertTrue(e.getMessage().contains("trino.jdbc.ssl.truststore.path"));
+ }
+}