diff --git a/docs/src/main/sphinx/connector/pinot.rst b/docs/src/main/sphinx/connector/pinot.rst index fcf3fa93e442..fbc9a7582231 100644 --- a/docs/src/main/sphinx/connector/pinot.rst +++ b/docs/src/main/sphinx/connector/pinot.rst @@ -33,18 +33,35 @@ Configuration properties The following configuration properties are available: -============================== ========== ============================================================================== -Property Name Required Description -============================== ========== ============================================================================== -``pinot.controller-urls`` Yes A comma separated list of controller hosts. If Pinot is deployed via - `Kubernetes `_ this needs to point to the controller - service endpoint. The Pinot broker and server must be accessible via DNS as - Pinot returns hostnames and not IP addresses. -``pinot.segments-per-split`` No The number of segments processed in a split. Setting this higher reduces the - number of requests made to Pinot. This is useful for smaller Pinot clusters. -``pinot.request-timeout`` No The timeout for Pinot requests. Increasing this can reduce timeouts if DNS - resolution is slow. -============================== ========== ============================================================================== +============================================= ========== ============================================================================== +Property Name Required Description +============================================= ========== ============================================================================== +``pinot.controller-urls`` Yes A comma separated list of controller hosts. If Pinot is deployed via + `Kubernetes `_ this needs to point to the controller + service endpoint. The Pinot broker and server must be accessible via DNS as + Pinot returns hostnames and not IP addresses. +``pinot.segments-per-split`` No The number of segments processed in a split. Setting this higher reduces the + number of requests made to Pinot. This is useful for smaller Pinot clusters. +``pinot.request-timeout`` No The timeout for Pinot requests. Increasing this can reduce timeouts if DNS + resolution is slow. +``pinot.controller.authentication.type`` No Pinot authentication method for controller requests. Allowed values are + ``NONE`` and ``PASSWORD`` - defaults to ``NONE`` which is no authentication. +``pinot.controller.authentication.user`` No Controller username for basic authentication method. +``pinot.controller.authentication.password`` No Controller password for basic authentication method. +``pinot.broker.authentication.type`` No Pinot authentication method for broker requests. Allowed values are + ``NONE`` and ``PASSWORD`` - defaults to ``NONE`` which is no + authentication. +``pinot.broker.authentication.user`` No Broker username for basic authentication method. +``pinot.broker.authentication.password`` No Broker password for basic authentication method. +============================================= ========== ============================================================================== + +If ``pinot.controller.authentication.type`` is set to ``PASSWORD`` then both ``pinot.controller.authentication.user`` and +``pinot.controller.authentication.password`` are required. + +If ``pinot.broker.authentication.type`` is set to ``PASSWORD`` then both ``pinot.broker.authentication.user`` and +``pinot.broker.authentication.password`` are required. + +You can use :doc:`secrets ` to avoid actual values in the catalog properties files. Querying Pinot tables --------------------- diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index c7849443697f..d15ec86e87de 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -103,6 +103,18 @@ guice + + + com.squareup.okhttp3 + okhttp + + + org.codehaus.mojo + * + + + + commons-codec commons-codec diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java index 4d00f92ef067..9956d2fbfc39 100755 --- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java @@ -19,6 +19,7 @@ import io.airlift.bootstrap.Bootstrap; import io.airlift.json.JsonModule; import io.trino.plugin.base.TypeDeserializerModule; +import io.trino.plugin.pinot.auth.PinotAuthenticationModule; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; @@ -62,7 +63,8 @@ public Connector create(String catalogName, Map config, Connecto .add(new JsonModule()) .add(new MBeanModule()) .add(new TypeDeserializerModule(context.getTypeManager())) - .add(new PinotModule(catalogName, context.getNodeManager())); + .add(new PinotModule(catalogName, context.getNodeManager())) + .add(new PinotAuthenticationModule()); extension.ifPresent(modulesBuilder::add); diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationModule.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationModule.java new file mode 100644 index 000000000000..f17b80d05f61 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationModule.java @@ -0,0 +1,127 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Provides; +import io.airlift.configuration.AbstractConfigurationAwareModule; +import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider; +import io.trino.plugin.pinot.auth.password.PinotPasswordAuthenticationProvider; +import io.trino.plugin.pinot.auth.password.inline.PinotPasswordBrokerAuthenticationConfig; +import io.trino.plugin.pinot.auth.password.inline.PinotPasswordControllerAuthenticationConfig; + +import javax.inject.Singleton; + +import static io.airlift.configuration.ConditionalModule.conditionalModule; +import static io.airlift.configuration.ConfigBinder.configBinder; +import static io.trino.plugin.pinot.auth.PinotAuthenticationType.NONE; +import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD; + +public class PinotAuthenticationModule + extends AbstractConfigurationAwareModule +{ + @Override + protected void setup(Binder binder) + { + bindAuthenticationProviderModule( + NONE, + new PinotNoneControllerAuthenticationProviderModule(), + new PinotNoneBrokerAuthenticationProviderModule()); + bindAuthenticationProviderModule( + PASSWORD, + new PinotPasswordControllerAuthenticationProviderModule(), + new PinotPasswordBrokerAuthenticationProviderModule()); + } + + private void bindAuthenticationProviderModule(PinotAuthenticationType authType, Module controllerModule, Module brokerModule) + { + install(conditionalModule( + PinotAuthenticationTypeConfig.class, + config -> authType == config.getControllerAuthenticationType(), + controllerModule)); + install(conditionalModule( + PinotAuthenticationTypeConfig.class, + config -> authType == config.getBrokerAuthenticationType(), + brokerModule)); + } + + private static class PinotNoneControllerAuthenticationProviderModule + implements Module + { + @Override + public void configure(Binder binder) + { + } + + @Provides + @Singleton + public PinotControllerAuthenticationProvider getAuthenticationProvider() + { + return PinotControllerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()); + } + } + + private static class PinotNoneBrokerAuthenticationProviderModule + implements Module + { + @Override + public void configure(Binder binder) + { + } + + @Provides + @Singleton + public PinotBrokerAuthenticationProvider getAuthenticationProvider() + { + return PinotBrokerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()); + } + } + + private static class PinotPasswordControllerAuthenticationProviderModule + implements Module + { + @Override + public void configure(Binder binder) + { + configBinder(binder).bindConfig(PinotPasswordControllerAuthenticationConfig.class); + } + + @Provides + @Singleton + public PinotControllerAuthenticationProvider getAuthenticationProvider( + PinotPasswordControllerAuthenticationConfig config) + { + return PinotControllerAuthenticationProvider.create(new PinotPasswordAuthenticationProvider(config.getUser(), config.getPassword())); + } + } + + private static class PinotPasswordBrokerAuthenticationProviderModule + implements Module + { + @Override + public void configure(Binder binder) + { + configBinder(binder).bindConfig(PinotPasswordBrokerAuthenticationConfig.class); + } + + @Provides + @Singleton + public PinotBrokerAuthenticationProvider getAuthenticationProvider( + PinotPasswordBrokerAuthenticationConfig config) + { + return PinotBrokerAuthenticationProvider.create(new PinotPasswordAuthenticationProvider(config.getUser(), config.getPassword())); + } + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationProvider.java new file mode 100644 index 000000000000..27be84c4e7d0 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationProvider.java @@ -0,0 +1,21 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +import java.util.Optional; + +public interface PinotAuthenticationProvider +{ + Optional getAuthenticationToken(); +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationType.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationType.java new file mode 100644 index 000000000000..f10a78cd6aea --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationType.java @@ -0,0 +1,19 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +public enum PinotAuthenticationType { + NONE, + PASSWORD, +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationTypeConfig.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationTypeConfig.java new file mode 100644 index 000000000000..8827ddece888 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationTypeConfig.java @@ -0,0 +1,50 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +import io.airlift.configuration.Config; + +import javax.validation.constraints.NotNull; + +public class PinotAuthenticationTypeConfig +{ + private PinotAuthenticationType controllerAuthenticationType = PinotAuthenticationType.NONE; + private PinotAuthenticationType brokerAuthenticationType = PinotAuthenticationType.NONE; + + @NotNull + public PinotAuthenticationType getControllerAuthenticationType() + { + return controllerAuthenticationType; + } + + @Config("pinot.controller.authentication.type") + public PinotAuthenticationTypeConfig setControllerAuthenticationType(PinotAuthenticationType controllerAuthenticationType) + { + this.controllerAuthenticationType = controllerAuthenticationType; + return this; + } + + @NotNull + public PinotAuthenticationType getBrokerAuthenticationType() + { + return brokerAuthenticationType; + } + + @Config("pinot.broker.authentication.type") + public PinotAuthenticationTypeConfig setBrokerAuthenticationType(PinotAuthenticationType brokerAuthenticationType) + { + this.brokerAuthenticationType = brokerAuthenticationType; + return this; + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotBrokerAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotBrokerAuthenticationProvider.java new file mode 100644 index 000000000000..45070e5b7259 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotBrokerAuthenticationProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class PinotBrokerAuthenticationProvider + implements PinotAuthenticationProvider +{ + public static PinotBrokerAuthenticationProvider create(PinotAuthenticationProvider delegate) + { + return new PinotBrokerAuthenticationProvider(delegate); + } + + private final PinotAuthenticationProvider delegate; + + private PinotBrokerAuthenticationProvider(PinotAuthenticationProvider delegate) + { + this.delegate = requireNonNull(delegate, "Delegate broker authentication provider is required"); + } + + @Override + public Optional getAuthenticationToken() + { + return delegate.getAuthenticationToken(); + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotControllerAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotControllerAuthenticationProvider.java new file mode 100644 index 000000000000..4927ce609efe --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotControllerAuthenticationProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class PinotControllerAuthenticationProvider + implements PinotAuthenticationProvider +{ + public static PinotControllerAuthenticationProvider create(PinotAuthenticationProvider delegate) + { + return new PinotControllerAuthenticationProvider(delegate); + } + + private final PinotAuthenticationProvider delegate; + + private PinotControllerAuthenticationProvider(PinotAuthenticationProvider delegate) + { + this.delegate = requireNonNull(delegate, "Delegate controller authentication provider is required"); + } + + @Override + public Optional getAuthenticationToken() + { + return delegate.getAuthenticationToken(); + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/none/PinotEmptyAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/none/PinotEmptyAuthenticationProvider.java new file mode 100644 index 000000000000..68f36528e8c2 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/none/PinotEmptyAuthenticationProvider.java @@ -0,0 +1,37 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.none; + +import io.trino.plugin.pinot.auth.PinotAuthenticationProvider; + +import java.util.Optional; + +public class PinotEmptyAuthenticationProvider + implements PinotAuthenticationProvider +{ + private static final PinotEmptyAuthenticationProvider INSTANCE = new PinotEmptyAuthenticationProvider(); + + public static PinotEmptyAuthenticationProvider instance() + { + return INSTANCE; + } + + private PinotEmptyAuthenticationProvider() {} + + @Override + public Optional getAuthenticationToken() + { + return Optional.empty(); + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/PinotPasswordAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/PinotPasswordAuthenticationProvider.java new file mode 100644 index 000000000000..0c59b3e4fe98 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/PinotPasswordAuthenticationProvider.java @@ -0,0 +1,46 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.password; + +import io.trino.plugin.pinot.auth.PinotAuthenticationProvider; +import okhttp3.Credentials; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class PinotPasswordAuthenticationProvider + implements PinotAuthenticationProvider +{ + private final Optional authToken; + + public PinotPasswordAuthenticationProvider(String user, String password) + { + requireNonNull(user, "user is null"); + requireNonNull(password, "password is null"); + this.authToken = Optional.of(encode(user, password)); + } + + @Override + public Optional getAuthenticationToken() + { + return authToken; + } + + private String encode(String username, String password) + { + return Credentials.basic(username, password, StandardCharsets.ISO_8859_1); + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordBrokerAuthenticationConfig.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordBrokerAuthenticationConfig.java new file mode 100755 index 000000000000..f2124c06dc9b --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordBrokerAuthenticationConfig.java @@ -0,0 +1,52 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.password.inline; + +import io.airlift.configuration.Config; +import io.airlift.configuration.ConfigSecuritySensitive; + +import javax.validation.constraints.NotNull; + +public class PinotPasswordBrokerAuthenticationConfig +{ + private String user; + private String password; + + @NotNull + public String getUser() + { + return user; + } + + @Config("pinot.broker.authentication.user") + public PinotPasswordBrokerAuthenticationConfig setUser(String user) + { + this.user = user; + return this; + } + + @NotNull + public String getPassword() + { + return password; + } + + @Config("pinot.broker.authentication.password") + @ConfigSecuritySensitive + public PinotPasswordBrokerAuthenticationConfig setPassword(String password) + { + this.password = password; + return this; + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordControllerAuthenticationConfig.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordControllerAuthenticationConfig.java new file mode 100755 index 000000000000..05054437bf53 --- /dev/null +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordControllerAuthenticationConfig.java @@ -0,0 +1,52 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.password.inline; + +import io.airlift.configuration.Config; +import io.airlift.configuration.ConfigSecuritySensitive; + +import javax.validation.constraints.NotNull; + +public class PinotPasswordControllerAuthenticationConfig +{ + private String user; + private String password; + + @NotNull + public String getUser() + { + return user; + } + + @Config("pinot.controller.authentication.user") + public PinotPasswordControllerAuthenticationConfig setUser(String user) + { + this.user = user; + return this; + } + + @NotNull + public String getPassword() + { + return password; + } + + @Config("pinot.controller.authentication.password") + @ConfigSecuritySensitive + public PinotPasswordControllerAuthenticationConfig setPassword(String password) + { + this.password = password; + return this; + } +} diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java index 6cc8377a6b01..5b91c052fbd4 100755 --- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java @@ -24,7 +24,8 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.net.HttpHeaders; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; import io.airlift.http.client.HttpClient; import io.airlift.http.client.JsonResponseHandler; import io.airlift.http.client.Request; @@ -41,6 +42,8 @@ import io.trino.plugin.pinot.PinotException; import io.trino.plugin.pinot.PinotInsufficientServerResponseException; import io.trino.plugin.pinot.PinotSessionProperties; +import io.trino.plugin.pinot.auth.PinotBrokerAuthenticationProvider; +import io.trino.plugin.pinot.auth.PinotControllerAuthenticationProvider; import io.trino.plugin.pinot.query.PinotQueryInfo; import io.trino.spi.connector.ConnectorSession; import org.apache.pinot.common.response.broker.BrokerResponseNative; @@ -69,6 +72,9 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.net.HttpHeaders.ACCEPT; +import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; import static io.airlift.http.client.HttpUriBuilder.uriBuilderFrom; import static io.airlift.http.client.JsonResponseHandler.createJsonResponseHandler; import static io.airlift.json.JsonCodec.jsonCodec; @@ -110,6 +116,8 @@ public class PinotClient private final JsonCodec timeBoundaryJsonCodec; private final JsonCodec schemaJsonCodec; private final JsonCodec brokerResponseCodec; + private final PinotControllerAuthenticationProvider controllerAuthenticationProvider; + private final PinotBrokerAuthenticationProvider brokerAuthenticationProvider; @Inject public PinotClient( @@ -119,7 +127,9 @@ public PinotClient( JsonCodec tablesJsonCodec, JsonCodec brokersForTableJsonCodec, JsonCodec timeBoundaryJsonCodec, - JsonCodec brokerResponseCodec) + JsonCodec brokerResponseCodec, + PinotControllerAuthenticationProvider controllerAuthenticationProvider, + PinotBrokerAuthenticationProvider brokerAuthenticationProvider) { this.brokersForTableJsonCodec = requireNonNull(brokersForTableJsonCodec, "brokersForTableJsonCodec is null"); this.timeBoundaryJsonCodec = requireNonNull(timeBoundaryJsonCodec, "timeBoundaryJsonCodec is null"); @@ -138,6 +148,8 @@ public PinotClient( this.brokersForTableCache = CacheBuilder.newBuilder() .expireAfterWrite(config.getMetadataCacheExpiry().roundTo(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS) .build((CacheLoader.from(this::getAllBrokersForTable))); + this.controllerAuthenticationProvider = controllerAuthenticationProvider; + this.brokerAuthenticationProvider = brokerAuthenticationProvider; } public static JsonCodecBinder addJsonBinders(JsonCodecBinder jsonCodecBinder) @@ -150,11 +162,16 @@ public static JsonCodecBinder addJsonBinders(JsonCodecBinder jsonCodecBinder) return jsonCodecBinder; } - protected T doHttpActionWithHeadersJson(Request.Builder requestBuilder, Optional requestBody, JsonCodec codec) + protected T doHttpActionWithHeadersJson( + Request.Builder requestBuilder, + Optional requestBody, + JsonCodec codec, + Multimap additionalHeaders) { - requestBuilder.setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON); + requestBuilder.addHeaders(additionalHeaders); + requestBuilder.setHeader(ACCEPT, APPLICATION_JSON); if (requestBody.isPresent()) { - requestBuilder.setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + requestBuilder.setHeader(CONTENT_TYPE, APPLICATION_JSON); requestBuilder.setBodyGenerator(StaticBodyGenerator.createStaticBodyGenerator(requestBody.get(), StandardCharsets.UTF_8)); } Request request = requestBuilder.build(); @@ -180,18 +197,24 @@ protected T doHttpActionWithHeadersJson(Request.Builder requestBuilder, Opti private T sendHttpGetToControllerJson(String path, JsonCodec codec) { + ImmutableMultimap.Builder additionalHeadersBuilder = ImmutableMultimap.builder(); + controllerAuthenticationProvider.getAuthenticationToken().ifPresent(token -> additionalHeadersBuilder.put(AUTHORIZATION, token)); return doHttpActionWithHeadersJson( Request.Builder.prepareGet().setUri(uriBuilderFrom(getControllerUrl()).appendPath(path).build()), Optional.empty(), - codec); + codec, + additionalHeadersBuilder.build()); } private T sendHttpGetToBrokerJson(String table, String path, JsonCodec codec) { + ImmutableMultimap.Builder additionalHeadersBuilder = ImmutableMultimap.builder(); + brokerAuthenticationProvider.getAuthenticationToken().ifPresent(token -> additionalHeadersBuilder.put(AUTHORIZATION, token)); return doHttpActionWithHeadersJson( Request.Builder.prepareGet().setUri(URI.create(format("http://%s/%s", getBrokerHost(table), path))), Optional.empty(), - codec); + codec, + additionalHeadersBuilder.build()); } private URI getControllerUrl() @@ -448,7 +471,10 @@ private BrokerResponseNative submitBrokerQueryJson(ConnectorSession session, Pin LOG.info("Query '%s' on broker host '%s'", queryHost, query.getQuery()); Request.Builder builder = Request.Builder.preparePost() .setUri(URI.create(format(QUERY_URL_TEMPLATE, queryHost))); - BrokerResponseNative response = doHttpActionWithHeadersJson(builder, Optional.of(queryRequest), brokerResponseCodec); + ImmutableMultimap.Builder additionalHeadersBuilder = ImmutableMultimap.builder(); + brokerAuthenticationProvider.getAuthenticationToken().ifPresent(token -> additionalHeadersBuilder.put(AUTHORIZATION, token)); + BrokerResponseNative response = doHttpActionWithHeadersJson(builder, Optional.of(queryRequest), brokerResponseCodec, + additionalHeadersBuilder.build()); if (response.getExceptionsSize() > 0 && response.getProcessingExceptions() != null && !response.getProcessingExceptions().isEmpty()) { // Pinot is known to return exceptions with benign errorcodes like 200 diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotIntegrationSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/AbstractPinotIntegrationSmokeTest.java similarity index 99% rename from plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotIntegrationSmokeTest.java rename to plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/AbstractPinotIntegrationSmokeTest.java index e39caa072fa6..bddc8863fe53 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotIntegrationSmokeTest.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/AbstractPinotIntegrationSmokeTest.java @@ -65,7 +65,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; -public class TestPinotIntegrationSmokeTest +public abstract class AbstractPinotIntegrationSmokeTest // TODO extend BaseConnectorTest extends AbstractTestQueryFramework { @@ -87,13 +87,15 @@ public class TestPinotIntegrationSmokeTest // Use a fixed instant for testing date time functions private static final Instant CREATED_AT_INSTANT = Instant.parse("2021-05-10T00:00:00.00Z"); + protected abstract boolean isSecured(); + @Override protected QueryRunner createQueryRunner() throws Exception { TestingKafka kafka = closeAfterClass(TestingKafka.createWithSchemaRegistry()); kafka.start(); - TestingPinotCluster pinot = closeAfterClass(new TestingPinotCluster(kafka.getNetwork())); + TestingPinotCluster pinot = closeAfterClass(new TestingPinotCluster(kafka.getNetwork(), isSecured())); pinot.start(); // Create and populate the all_types topic and table @@ -294,17 +296,26 @@ protected QueryRunner createQueryRunner() pinot.createSchema(getClass().getClassLoader().getResourceAsStream("quotes_in_column_name_schema.json"), QUOTES_IN_COLUMN_NAME_TABLE); pinot.addRealTimeTable(getClass().getClassLoader().getResourceAsStream("quotes_in_column_name_realtimeSpec.json"), QUOTES_IN_COLUMN_NAME_TABLE); - Map pinotProperties = ImmutableMap.builder() + return PinotQueryRunner.createPinotQueryRunner( + ImmutableMap.of(), + pinotProperties(pinot), + Optional.of(binder -> newOptionalBinder(binder, PinotHostMapper.class).setBinding() + .toInstance(new TestingPinotHostMapper(pinot.getBrokerHostAndPort(), pinot.getServerHostAndPort())))); + } + + private Map pinotProperties(TestingPinotCluster pinot) + { + return ImmutableMap.builder() .put("pinot.controller-urls", pinot.getControllerConnectString()) .put("pinot.max-rows-per-split-for-segment-queries", String.valueOf(MAX_ROWS_PER_SPLIT_FOR_SEGMENT_QUERIES)) .put("pinot.max-rows-for-broker-queries", String.valueOf(MAX_ROWS_PER_SPLIT_FOR_BROKER_QUERIES)) + .putAll(additionalPinotProperties()) .buildOrThrow(); + } - return PinotQueryRunner.createPinotQueryRunner( - ImmutableMap.of(), - pinotProperties, - Optional.of(binder -> newOptionalBinder(binder, PinotHostMapper.class).setBinding() - .toInstance(new TestingPinotHostMapper(pinot.getBrokerHostAndPort(), pinot.getServerHostAndPort())))); + protected Map additionalPinotProperties() + { + return ImmutableMap.of(); } private static Map schemaRegistryAwareProducer(TestingKafka testingKafka) diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java index 2f393ae93a85..e7a9d11d27ea 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java @@ -15,9 +15,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; import io.airlift.http.client.Request; import io.airlift.http.client.testing.TestingHttpClient; import io.airlift.json.JsonCodec; +import io.trino.plugin.pinot.auth.PinotBrokerAuthenticationProvider; +import io.trino.plugin.pinot.auth.PinotControllerAuthenticationProvider; +import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider; import io.trino.plugin.pinot.client.IdentityPinotHostMapper; import io.trino.plugin.pinot.client.PinotClient; import org.apache.pinot.spi.data.Schema; @@ -57,7 +61,9 @@ public MockPinotClient(PinotConfig pinotConfig, Map metadata, St TABLES_JSON_CODEC, BROKERS_FOR_TABLE_JSON_CODEC, TIME_BOUNDARY_JSON_CODEC, - BROKER_RESPONSE_NATIVE_JSON_CODEC); + BROKER_RESPONSE_NATIVE_JSON_CODEC, + PinotControllerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()), + PinotBrokerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance())); this.metadata = metadata; this.response = response; } @@ -69,7 +75,11 @@ public String getBrokerHost(String table) } @Override - public T doHttpActionWithHeadersJson(Request.Builder requestBuilder, Optional requestBody, JsonCodec codec) + public T doHttpActionWithHeadersJson( + Request.Builder requestBuilder, + Optional requestBody, + JsonCodec codec, + Multimap additionalHeaders) { return codec.fromJson(response); } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java index c14f6897f3df..683740323121 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java @@ -32,9 +32,7 @@ public class PinotQueryRunner { public static final String PINOT_CATALOG = "pinot"; - private PinotQueryRunner() - { - } + private PinotQueryRunner() {} public static DistributedQueryRunner createPinotQueryRunner(Map extraProperties, Map extraPinotProperties, Optional extension) throws Exception @@ -70,7 +68,7 @@ public static void main(String[] args) Logging.initialize(); TestingKafka kafka = TestingKafka.createWithSchemaRegistry(); kafka.start(); - TestingPinotCluster pinot = new TestingPinotCluster(kafka.getNetwork()); + TestingPinotCluster pinot = new TestingPinotCluster(kafka.getNetwork(), false); pinot.start(); Map properties = ImmutableMap.of("http-server.http.port", "8080"); Map pinotProperties = ImmutableMap.builder() diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java index 3fc2e6f7a67c..e5d9e1f5d8b0 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java @@ -20,6 +20,9 @@ import io.airlift.http.client.testing.TestingHttpClient; import io.airlift.http.client.testing.TestingResponse; import io.airlift.units.Duration; +import io.trino.plugin.pinot.auth.PinotBrokerAuthenticationProvider; +import io.trino.plugin.pinot.auth.PinotControllerAuthenticationProvider; +import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider; import io.trino.plugin.pinot.client.IdentityPinotHostMapper; import io.trino.plugin.pinot.client.PinotClient; import io.trino.testing.assertions.Assert; @@ -74,7 +77,16 @@ public void testBrokersParsed() PinotConfig pinotConfig = new PinotConfig() .setMetadataCacheExpiry(new Duration(0, TimeUnit.MILLISECONDS)) .setControllerUrls("localhost:7900"); - PinotClient pinotClient = new PinotClient(pinotConfig, new IdentityPinotHostMapper(), httpClient, MetadataUtil.TABLES_JSON_CODEC, MetadataUtil.BROKERS_FOR_TABLE_JSON_CODEC, MetadataUtil.TIME_BOUNDARY_JSON_CODEC, MetadataUtil.BROKER_RESPONSE_NATIVE_JSON_CODEC); + PinotClient pinotClient = new PinotClient( + pinotConfig, + new IdentityPinotHostMapper(), + httpClient, + MetadataUtil.TABLES_JSON_CODEC, + MetadataUtil.BROKERS_FOR_TABLE_JSON_CODEC, + MetadataUtil.TIME_BOUNDARY_JSON_CODEC, + MetadataUtil.BROKER_RESPONSE_NATIVE_JSON_CODEC, + PinotControllerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()), + PinotBrokerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance())); ImmutableSet brokers = ImmutableSet.copyOf(pinotClient.getAllBrokersForTable("dummy")); Assert.assertEquals(ImmutableSet.of("dummy-broker-host1-datacenter1:6513", "dummy-broker-host2-datacenter1:6513", "dummy-broker-host3-datacenter1:6513", "dummy-broker-host4-datacenter1:6513"), brokers); } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotWithoutAuthenticationIntegrationSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotWithoutAuthenticationIntegrationSmokeTest.java new file mode 100644 index 000000000000..54d1008c3475 --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotWithoutAuthenticationIntegrationSmokeTest.java @@ -0,0 +1,24 @@ +/* + * Licensed 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 io.trino.plugin.pinot; + +public class TestPinotWithoutAuthenticationIntegrationSmokeTest + extends AbstractPinotIntegrationSmokeTest +{ + @Override + protected boolean isSecured() + { + return false; + } +} diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestSecuredPinotIntegrationSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestSecuredPinotIntegrationSmokeTest.java new file mode 100644 index 000000000000..189b6236a327 --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestSecuredPinotIntegrationSmokeTest.java @@ -0,0 +1,43 @@ +/* + * Licensed 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 io.trino.plugin.pinot; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD; + +public class TestSecuredPinotIntegrationSmokeTest + extends AbstractPinotIntegrationSmokeTest +{ + @Override + protected boolean isSecured() + { + return true; + } + + @Override + protected Map additionalPinotProperties() + { + return ImmutableMap.builder() + .put("pinot.controller.authentication.type", PASSWORD.name()) + .put("pinot.controller.authentication.user", "admin") + .put("pinot.controller.authentication.password", "verysecret") + .put("pinot.broker.authentication.type", PASSWORD.name()) + .put("pinot.broker.authentication.user", "query") + .put("pinot.broker.authentication.password", "secret") + .buildOrThrow(); + } +} diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java index 399029a3d84d..fb935fb983c0 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java @@ -24,6 +24,7 @@ import io.airlift.http.client.StaticBodyGenerator; import io.airlift.http.client.jetty.JettyHttpClient; import io.airlift.json.JsonCodec; +import okhttp3.Credentials; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; @@ -32,6 +33,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.function.Supplier; @@ -64,8 +66,9 @@ public class TestingPinotCluster private final GenericContainer zookeeper; private final HttpClient httpClient; private final Closer closer = Closer.create(); + private final boolean secured; - public TestingPinotCluster(Network network) + public TestingPinotCluster(Network network, boolean secured) { httpClient = closer.register(new JettyHttpClient()); zookeeper = new GenericContainer<>(parse("zookeeper:3.5.6")) @@ -75,20 +78,22 @@ public TestingPinotCluster(Network network) .withExposedPorts(ZOOKEEPER_PORT); closer.register(zookeeper::stop); + String controllerConfig = secured ? "/var/pinot/controller/config/pinot-controller-secured.conf" : "/var/pinot/controller/config/pinot-controller.conf"; controller = new GenericContainer<>(parse(BASE_IMAGE)) .withNetwork(network) .withClasspathResourceMapping("/pinot-controller", "/var/pinot/controller/config", BindMode.READ_ONLY) .withEnv("JAVA_OPTS", "-Xmx512m -Dlog4j2.configurationFile=/opt/pinot/conf/pinot-controller-log4j2.xml -Dplugins.dir=/opt/pinot/plugins") - .withCommand("StartController", "-configFileName", "/var/pinot/controller/config/pinot-controller.conf") + .withCommand("StartController", "-configFileName", controllerConfig) .withNetworkAliases("pinot-controller", "localhost") .withExposedPorts(CONTROLLER_PORT); closer.register(controller::stop); + String brokerConfig = secured ? "/var/pinot/broker/config/pinot-broker-secured.conf" : "/var/pinot/broker/config/pinot-broker.conf"; broker = new GenericContainer<>(parse(BASE_IMAGE)) .withNetwork(network) .withClasspathResourceMapping("/pinot-broker", "/var/pinot/broker/config", BindMode.READ_ONLY) .withEnv("JAVA_OPTS", "-Xmx512m -Dlog4j2.configurationFile=/opt/pinot/conf/pinot-broker-log4j2.xml -Dplugins.dir=/opt/pinot/plugins") - .withCommand("StartBroker", "-clusterName", "pinot", "-zkAddress", getZookeeperInternalHostPort(), "-configFileName", "/var/pinot/broker/config/pinot-broker.conf") + .withCommand("StartBroker", "-clusterName", "pinot", "-zkAddress", getZookeeperInternalHostPort(), "-configFileName", brokerConfig) .withNetworkAliases("pinot-broker", "localhost") .withExposedPorts(BROKER_PORT); closer.register(broker::stop); @@ -101,6 +106,8 @@ public TestingPinotCluster(Network network) .withNetworkAliases("pinot-server", "localhost") .withExposedPorts(SERVER_PORT, SERVER_ADMIN_PORT); closer.register(server::stop); + + this.secured = secured; } public void start() @@ -146,6 +153,7 @@ public void createSchema(InputStream tableSchemaSpec, String tableName) .setUri(getControllerUri("schemas")) .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON) .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .addHeader(HttpHeaders.AUTHORIZATION, secured ? controllerAuthToken() : "") .setBodyGenerator(StaticBodyGenerator.createStaticBodyGenerator(bytes)) .build(); @@ -164,6 +172,7 @@ private void verifySchema(String tableName) { Request request = Request.Builder.prepareGet().setUri(getControllerUri("schemas")) .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON) + .addHeader(HttpHeaders.AUTHORIZATION, secured ? controllerAuthToken() : "") .build(); doWithRetries(() -> { List schemas = httpClient.execute(request, createJsonResponseHandler(LIST_JSON_CODEC)); @@ -180,6 +189,7 @@ public void addRealTimeTable(InputStream realTimeSpec, String tableName) .setUri(getControllerUri("tables")) .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON) .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .addHeader(HttpHeaders.AUTHORIZATION, secured ? controllerAuthToken() : "") .setBodyGenerator(StaticBodyGenerator.createStaticBodyGenerator(bytes)) .build(); @@ -204,6 +214,12 @@ private static T doWithRetries(Supplier supplier, int retries) throw exception; } + private static String controllerAuthToken() + { + // Secrets defined in pinot-controller-secured.conf + return Credentials.basic("admin", "verysecret", StandardCharsets.ISO_8859_1); + } + public static class PinotSuccessResponse { private final String status; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java new file mode 100644 index 000000000000..01820e6b60d5 --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java @@ -0,0 +1,52 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth; + +import com.google.common.collect.ImmutableMap; +import org.testng.annotations.Test; + +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; +import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; +import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static io.trino.plugin.pinot.auth.PinotAuthenticationType.NONE; +import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD; + +public class TestPinotAuthenticationTypeConfig +{ + @Test + public void testDefaults() + { + assertRecordedDefaults( + recordDefaults(PinotAuthenticationTypeConfig.class) + .setControllerAuthenticationType(NONE) + .setBrokerAuthenticationType(NONE)); + } + + @Test + public void testExplicitPropertyMappings() + { + Map properties = new ImmutableMap.Builder() + .put("pinot.controller.authentication.type", "password") + .put("pinot.broker.authentication.type", "password") + .build(); + + PinotAuthenticationTypeConfig expected = new PinotAuthenticationTypeConfig() + .setControllerAuthenticationType(PASSWORD) + .setBrokerAuthenticationType(PASSWORD); + + assertFullMapping(properties, expected); + } +} diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java new file mode 100644 index 000000000000..18ab1701f557 --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java @@ -0,0 +1,30 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.none; + +import org.testng.annotations.Test; + +import java.util.Optional; + +import static org.testng.Assert.assertEquals; + +public class TestPinotEmptyAuthenticationProvider +{ + @Test + public void testAuthenticationToken() + { + PinotEmptyAuthenticationProvider underTest = PinotEmptyAuthenticationProvider.instance(); + assertEquals(underTest.getAuthenticationToken(), Optional.empty()); + } +} diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java new file mode 100644 index 000000000000..501bbb2289db --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java @@ -0,0 +1,30 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.password; + +import org.testng.annotations.Test; + +import java.util.Optional; + +import static org.testng.Assert.assertEquals; + +public class TestPinotPasswordAuthenticationProvider +{ + @Test + public void testAuthenticationToken() + { + PinotPasswordAuthenticationProvider underTest = new PinotPasswordAuthenticationProvider("admin", "verysecret"); + assertEquals(underTest.getAuthenticationToken(), Optional.of("Basic YWRtaW46dmVyeXNlY3JldA==")); + } +} diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java new file mode 100644 index 000000000000..ba9afbc1459b --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java @@ -0,0 +1,39 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.password.inline; + +import com.google.common.collect.ImmutableMap; +import org.testng.annotations.Test; + +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; + +public class TestPinotPasswordBrokerAuthenticationConfig +{ + @Test + public void testExplicitPropertyMappings() + { + Map properties = new ImmutableMap.Builder() + .put("pinot.broker.authentication.user", "query") + .put("pinot.broker.authentication.password", "secret") + .build(); + + PinotPasswordBrokerAuthenticationConfig expected = new PinotPasswordBrokerAuthenticationConfig() + .setUser("query") + .setPassword("secret"); + + assertFullMapping(properties, expected); + } +} diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java new file mode 100644 index 000000000000..4c68698ec587 --- /dev/null +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java @@ -0,0 +1,39 @@ +/* + * Licensed 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 io.trino.plugin.pinot.auth.password.inline; + +import com.google.common.collect.ImmutableMap; +import org.testng.annotations.Test; + +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; + +public class TestPinotPasswordControllerAuthenticationConfig +{ + @Test + public void testExplicitPropertyMappings() + { + Map properties = new ImmutableMap.Builder() + .put("pinot.controller.authentication.user", "admin") + .put("pinot.controller.authentication.password", "verysecret") + .build(); + + PinotPasswordControllerAuthenticationConfig expected = new PinotPasswordControllerAuthenticationConfig() + .setUser("admin") + .setPassword("verysecret"); + + assertFullMapping(properties, expected); + } +} diff --git a/plugin/trino-pinot/src/test/resources/pinot-broker/pinot-broker-secured.conf b/plugin/trino-pinot/src/test/resources/pinot-broker/pinot-broker-secured.conf new file mode 100644 index 000000000000..bbc9d7b771ec --- /dev/null +++ b/plugin/trino-pinot/src/test/resources/pinot-broker/pinot-broker-secured.conf @@ -0,0 +1,6 @@ +pinot.broker.client.queryPort=8099 +pinot.broker.routing.table.builder.class=random +pinot.set.instance.id.to.hostname=true +pinot.broker.access.control.class=org.apache.pinot.broker.broker.BasicAuthAccessControlFactory +pinot.broker.access.control.principals=query +pinot.broker.access.control.principals.query.password=secret diff --git a/plugin/trino-pinot/src/test/resources/pinot-controller/pinot-controller-secured.conf b/plugin/trino-pinot/src/test/resources/pinot-controller/pinot-controller-secured.conf new file mode 100644 index 000000000000..1220c4559b3a --- /dev/null +++ b/plugin/trino-pinot/src/test/resources/pinot-controller/pinot-controller-secured.conf @@ -0,0 +1,10 @@ +controller.helix.cluster.name=pinot +controller.host=pinot-controller +controller.port=9000 +controller.data.dir=/var/pinot/controller/data/data +controller.local.temp.dir=/var/pinot/controller/data +controller.zk.str=zookeeper:2181 +pinot.set.instance.id.to.hostname=true +controller.admin.access.control.factory.class=org.apache.pinot.controller.api.access.BasicAuthAccessControlFactory +controller.admin.access.control.principals=admin +controller.admin.access.control.principals.admin.password=verysecret