From 434aace5e6c5f5ecfb8cc2622961ba03707a680f Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:25:42 -0700 Subject: [PATCH] Included Authenticators (#988) (#1005) Signed-off-by: vamsi-amazon Co-authored-by: vamsi-amazon --- .../model/AbstractAuthenticationData.java | 32 ----- .../sql/catalog/model/AuthenticationType.java | 10 -- .../model/BasicAuthenticationData.java | 25 ---- .../sql/catalog/model/CatalogMetadata.java | 7 +- .../model/auth/AuthenticationType.java | 42 ++++++ .../sql/storage/StorageEngineFactory.java | 19 +++ docs/user/ppl/admin/catalog.rst | 31 ++-- docs/user/ppl/admin/prometheus_connector.rst | 74 ++++++++++ docs/user/ppl/index.rst | 2 + doctest/catalog/catalog.json | 4 +- integ-test/build.gradle | 5 + .../src/test/resources/catalog/catalog.json | 4 +- plugin/build.gradle | 3 + .../plugin/catalog/CatalogServiceImpl.java | 72 +++++----- .../plugin-metadata/plugin-security.policy | 1 + .../catalog/CatalogServiceImplTest.java | 26 ++-- .../test/resources/catalog_missing_name.json | 10 +- plugin/src/test/resources/catalogs.json | 10 +- .../resources/duplicate_catalog_names.json | 20 +-- .../test/resources/illegal_catalog_name.json | 10 +- .../src/test/resources/multiple_catalogs.json | 21 +-- prometheus/build.gradle | 12 +- .../AwsSigningInterceptor.java | 59 ++++++++ .../BasicAuthenticationInterceptor.java | 34 +++++ .../client/PrometheusClientImpl.java | 7 +- .../request/PrometheusQueryRequest.java | 3 - .../storage/PrometheusStorageFactory.java | 95 ++++++++++++ .../AwsSigningInterceptorTest.java | 61 ++++++++ .../BasicAuthenticationInterceptorTest.java | 61 ++++++++ .../client/PrometheusClientImplTest.java | 3 +- .../storage/PrometheusStorageFactoryTest.java | 135 ++++++++++++++++++ 31 files changed, 708 insertions(+), 190 deletions(-) delete mode 100644 core/src/main/java/org/opensearch/sql/catalog/model/AbstractAuthenticationData.java delete mode 100644 core/src/main/java/org/opensearch/sql/catalog/model/AuthenticationType.java delete mode 100644 core/src/main/java/org/opensearch/sql/catalog/model/BasicAuthenticationData.java create mode 100644 core/src/main/java/org/opensearch/sql/catalog/model/auth/AuthenticationType.java create mode 100644 core/src/main/java/org/opensearch/sql/storage/StorageEngineFactory.java create mode 100644 docs/user/ppl/admin/prometheus_connector.rst create mode 100644 prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptor.java create mode 100644 prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptor.java create mode 100644 prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactory.java create mode 100644 prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptorTest.java create mode 100644 prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptorTest.java create mode 100644 prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactoryTest.java diff --git a/core/src/main/java/org/opensearch/sql/catalog/model/AbstractAuthenticationData.java b/core/src/main/java/org/opensearch/sql/catalog/model/AbstractAuthenticationData.java deleted file mode 100644 index e6a0dfa538..0000000000 --- a/core/src/main/java/org/opensearch/sql/catalog/model/AbstractAuthenticationData.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.sql.catalog.model; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import lombok.Getter; -import lombok.Setter; - -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - defaultImpl = AbstractAuthenticationData.class, - visible = true) -@JsonSubTypes({ - @JsonSubTypes.Type(value = BasicAuthenticationData.class, name = "basicauth"), -}) -@Getter -@Setter -public abstract class AbstractAuthenticationData { - - @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - private AuthenticationType type; - -} diff --git a/core/src/main/java/org/opensearch/sql/catalog/model/AuthenticationType.java b/core/src/main/java/org/opensearch/sql/catalog/model/AuthenticationType.java deleted file mode 100644 index 3e602c7f62..0000000000 --- a/core/src/main/java/org/opensearch/sql/catalog/model/AuthenticationType.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.sql.catalog.model; - -public enum AuthenticationType { - BASICAUTH,NO -} diff --git a/core/src/main/java/org/opensearch/sql/catalog/model/BasicAuthenticationData.java b/core/src/main/java/org/opensearch/sql/catalog/model/BasicAuthenticationData.java deleted file mode 100644 index 5ac8a72085..0000000000 --- a/core/src/main/java/org/opensearch/sql/catalog/model/BasicAuthenticationData.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.sql.catalog.model; - - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@JsonIgnoreProperties(ignoreUnknown = true) -public class BasicAuthenticationData extends AbstractAuthenticationData { - - @JsonProperty(required = true) - private String username; - - @JsonProperty(required = true) - private String password; - -} diff --git a/core/src/main/java/org/opensearch/sql/catalog/model/CatalogMetadata.java b/core/src/main/java/org/opensearch/sql/catalog/model/CatalogMetadata.java index 46c1894f6c..a859090a5d 100644 --- a/core/src/main/java/org/opensearch/sql/catalog/model/CatalogMetadata.java +++ b/core/src/main/java/org/opensearch/sql/catalog/model/CatalogMetadata.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; import lombok.Getter; import lombok.Setter; @@ -19,13 +20,11 @@ public class CatalogMetadata { @JsonProperty(required = true) private String name; - @JsonProperty(required = true) - private String uri; - @JsonProperty(required = true) @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) private ConnectorType connector; - private AbstractAuthenticationData authentication; + @JsonProperty(required = true) + private Map properties; } diff --git a/core/src/main/java/org/opensearch/sql/catalog/model/auth/AuthenticationType.java b/core/src/main/java/org/opensearch/sql/catalog/model/auth/AuthenticationType.java new file mode 100644 index 0000000000..1157d8e497 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/catalog/model/auth/AuthenticationType.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.catalog.model.auth; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public enum AuthenticationType { + + BASICAUTH("basicauth"), AWSSIGV4AUTH("awssigv4"); + + private String name; + + private static final Map ENUM_MAP; + + AuthenticationType(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + static { + Map map = new HashMap<>(); + for (AuthenticationType instance : AuthenticationType.values()) { + map.put(instance.getName().toLowerCase(), instance); + } + ENUM_MAP = Collections.unmodifiableMap(map); + } + + public static AuthenticationType get(String name) { + return ENUM_MAP.get(name.toLowerCase()); + } +} diff --git a/core/src/main/java/org/opensearch/sql/storage/StorageEngineFactory.java b/core/src/main/java/org/opensearch/sql/storage/StorageEngineFactory.java new file mode 100644 index 0000000000..4cc27f6fa0 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/storage/StorageEngineFactory.java @@ -0,0 +1,19 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.storage; + +import java.util.Map; +import org.opensearch.sql.catalog.model.ConnectorType; + +public interface StorageEngineFactory { + + ConnectorType getConnectorType(); + + StorageEngine getStorageEngine(String catalogName, Map requiredConfig); + +} diff --git a/docs/user/ppl/admin/catalog.rst b/docs/user/ppl/admin/catalog.rst index 7b0a08f307..ccaab342a5 100644 --- a/docs/user/ppl/admin/catalog.rst +++ b/docs/user/ppl/admin/catalog.rst @@ -26,21 +26,22 @@ Definitions of catalog and connector Example Prometheus Catalog Definition :: [{ - "name" : "prometheus", + "name" : "my_prometheus", "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "admin" + "properties" : { + "prometheus.uri" : "http://localhost:8080", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "admin" } }] Catalog configuration Restrictions. -* ``name``, ``uri``, ``connector`` are required fields in the catalog configuration. -* All the catalog names should be unique. -* Catalog names should match with the regex of an identifier[``[@*A-Za-z]+?[*a-zA-Z_\-0-9]*``]. -* ``prometheus`` is the only connector allowed. +* ``name``, ``connector``, ``properties`` are required fields in the catalog configuration. +* All the catalog names should be unique and match the following regex[``[@*A-Za-z]+?[*a-zA-Z_\-0-9]*``]. +* Allowed Connectors. + * ``prometheus`` [More details: `Prometheus Connector `_] +* All the allowed config parameters in ``properties`` are defined in individual connector pages mentioned above. Configuring catalog in OpenSearch ==================================== @@ -73,14 +74,10 @@ so we can refer a metric and apply stats over it in the following way. Example source command with prometheus catalog :: - >> source = prometheus.prometheus_http_requests_total | stats avg(@value) by job; + >> source = my_prometheus.prometheus_http_requests_total | stats avg(@value) by job; Limitations of catalog ==================================== -* Catalog settings are global and all PPL users are allowed to fetch data from all the defined catalogs. -* In each catalog, PPL users can access all the data available with the credentials provided in the catalog definition. -* With the current release, Basic and AWSSigV4 are the only authentication mechanisms supported with the underlying data sources. - - - +Catalog settings are global and users with PPL access are allowed to fetch data from all the defined catalogs. +PPL access can be controlled using roles.(More details: `Security Settings `_) \ No newline at end of file diff --git a/docs/user/ppl/admin/prometheus_connector.rst b/docs/user/ppl/admin/prometheus_connector.rst new file mode 100644 index 0000000000..53c862dfae --- /dev/null +++ b/docs/user/ppl/admin/prometheus_connector.rst @@ -0,0 +1,74 @@ +.. highlight:: sh + +==================== +Prometheus Connector +==================== + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 1 + + +Introduction +============ + +This page covers prometheus connector properties for catalog configuration +and the nuances associated with prometheus connector. + + +Prometheus Connector Properties in Catalog Configuration +======================================================== +Prometheus Connector Properties. + +* ``prometheus.uri`` [Required]. + * This parameters provides the URI information to connect to a prometheus instance. +* ``prometheus.auth.type`` [Optional] + * This parameters provides the authentication type information. + * Prometheus connector currently supports ``basicauth`` and ``awssigv4`` authentication mechanisms. + * If prometheus.auth.type is basicauth, following are required parameters. + * ``prometheus.auth.username`` and ``prometheus.auth.password``. + * If prometheus.auth.type is awssigv4, following are required parameters. + * ``prometheus.auth.region``, ``prometheus.auth.access_key`` and ``prometheus.auth.secret_key`` + +Example prometheus catalog configuration with different authentications +======================================================================= + +No Auth :: + + [{ + "name" : "my_prometheus", + "connector": "prometheus", + "properties" : { + "prometheus.uri" : "http://localhost:9090" + } + }] + +Basic Auth :: + + [{ + "name" : "my_prometheus", + "connector": "prometheus", + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "admin" + } + }] + +AWSSigV4 Auth:: + + [{ + "name" : "my_prometheus", + "connector": "prometheus", + "properties" : { + "prometheus.uri" : "http://localhost:8080", + "prometheus.auth.type" : "awssigv4", + "prometheus.auth.region" : "us-east-1", + "prometheus.auth.access_key" : "{{accessKey}}" + "prometheus.auth.secret_key" : "{{secretKey}}" + } + }] + diff --git a/docs/user/ppl/index.rst b/docs/user/ppl/index.rst index 3165569908..a4f620173c 100644 --- a/docs/user/ppl/index.rst +++ b/docs/user/ppl/index.rst @@ -36,6 +36,8 @@ The query start with search command and then flowing a set of command delimited - `Catalog Settings `_ + - `Prometheus Connector `_ + * **Commands** - `Syntax `_ diff --git a/doctest/catalog/catalog.json b/doctest/catalog/catalog.json index 84038fe83b..5f195747ae 100644 --- a/doctest/catalog/catalog.json +++ b/doctest/catalog/catalog.json @@ -2,6 +2,8 @@ { "name" : "my_prometheus", "connector": "prometheus", - "uri" : "http://localhost:9090" + "properties" : { + "prometheus.uri" : "http://localhost:9090" + } } ] \ No newline at end of file diff --git a/integ-test/build.gradle b/integ-test/build.gradle index bb217b463c..11ba5542fd 100644 --- a/integ-test/build.gradle +++ b/integ-test/build.gradle @@ -38,6 +38,10 @@ apply plugin: 'java' apply plugin: 'io.freefair.lombok' apply plugin: 'com.wiredforcode.spawn' +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} ext { projectSubstitutions = [:] @@ -63,6 +67,7 @@ configurations.all { resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${jackson_databind_version}" resolutionStrategy.force "org.jetbrains.kotlin:kotlin-stdlib:1.6.0" resolutionStrategy.force "org.jetbrains.kotlin:kotlin-stdlib-common:1.6.0" + resolutionStrategy.force "com.squareup.okhttp3:okhttp:4.9.3" } dependencies { diff --git a/integ-test/src/test/resources/catalog/catalog.json b/integ-test/src/test/resources/catalog/catalog.json index 84038fe83b..5f195747ae 100644 --- a/integ-test/src/test/resources/catalog/catalog.json +++ b/integ-test/src/test/resources/catalog/catalog.json @@ -2,6 +2,8 @@ { "name" : "my_prometheus", "connector": "prometheus", - "uri" : "http://localhost:9090" + "properties" : { + "prometheus.uri" : "http://localhost:9090" + } } ] \ No newline at end of file diff --git a/plugin/build.gradle b/plugin/build.gradle index 9de6a474cf..6a0900c3cc 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -39,6 +39,7 @@ ext { repositories { mavenCentral() + maven { url 'https://jitpack.io' } } opensearchplugin { @@ -91,6 +92,7 @@ configurations.all { resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${jackson_databind_version}" resolutionStrategy.force "org.jetbrains.kotlin:kotlin-stdlib:1.6.0" resolutionStrategy.force "org.jetbrains.kotlin:kotlin-stdlib-common:1.6.0" + resolutionStrategy.force "com.squareup.okhttp3:okhttp:4.9.3" } compileJava { options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor']) @@ -106,6 +108,7 @@ dependencies { api "com.fasterxml.jackson.core:jackson-databind:${jackson_databind_version}" api "com.fasterxml.jackson.core:jackson-annotations:${jackson_version}" + api project(":ppl") api project(':legacy') api project(':opensearch') diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/catalog/CatalogServiceImpl.java b/plugin/src/main/java/org/opensearch/sql/plugin/catalog/CatalogServiceImpl.java index c462c5a596..4f2f5e0f57 100644 --- a/plugin/src/main/java/org/opensearch/sql/plugin/catalog/CatalogServiceImpl.java +++ b/plugin/src/main/java/org/opensearch/sql/plugin/catalog/CatalogServiceImpl.java @@ -12,15 +12,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.security.PrivilegedExceptionAction; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import okhttp3.OkHttpClient; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,10 +28,9 @@ import org.opensearch.sql.catalog.model.CatalogMetadata; import org.opensearch.sql.catalog.model.ConnectorType; import org.opensearch.sql.opensearch.security.SecurityAccess; -import org.opensearch.sql.prometheus.client.PrometheusClient; -import org.opensearch.sql.prometheus.client.PrometheusClientImpl; -import org.opensearch.sql.prometheus.storage.PrometheusStorageEngine; +import org.opensearch.sql.prometheus.storage.PrometheusStorageFactory; import org.opensearch.sql.storage.StorageEngine; +import org.opensearch.sql.storage.StorageEngineFactory; /** * This class manages catalogs and responsible for creating connectors to these catalogs. @@ -48,11 +45,17 @@ public class CatalogServiceImpl implements CatalogService { private Map catalogMap = new HashMap<>(); + private final Map connectorTypeStorageEngineFactoryMap; + public static CatalogServiceImpl getInstance() { return INSTANCE; } private CatalogServiceImpl() { + connectorTypeStorageEngineFactoryMap = new HashMap<>(); + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + connectorTypeStorageEngineFactoryMap.put(prometheusStorageFactory.getConnectorType(), + prometheusStorageFactory); } /** @@ -71,13 +74,12 @@ public void loadConnectors(Settings settings) { List catalogs = objectMapper.readValue(inputStream, new TypeReference<>() { }); - LOG.info(catalogs.toString()); validateCatalogs(catalogs); constructConnectors(catalogs); } catch (IOException e) { - LOG.error("Catalog Configuration File uploaded is malformed. Verify and re-upload."); - throw new IllegalArgumentException( - "Malformed Catalog Configuration Json" + e.getMessage()); + LOG.error("Catalog Configuration File uploaded is malformed. Verify and re-upload.", e); + } catch (Throwable e) { + LOG.error("Catalog constructed failed.", e); } } return null; @@ -116,35 +118,31 @@ private T doPrivileged(PrivilegedExceptionAction action) { } } - private StorageEngine createStorageEngine(CatalogMetadata catalog) throws URISyntaxException { - StorageEngine storageEngine; + private StorageEngine createStorageEngine(CatalogMetadata catalog) { ConnectorType connector = catalog.getConnector(); switch (connector) { case PROMETHEUS: - PrometheusClient - prometheusClient = - new PrometheusClientImpl(new OkHttpClient(), - new URI(catalog.getUri())); - storageEngine = new PrometheusStorageEngine(prometheusClient); - break; + return connectorTypeStorageEngineFactoryMap + .get(catalog.getConnector()) + .getStorageEngine(catalog.getName(), catalog.getProperties()); default: - LOG.info( - "Unknown connector \"{}\". " - + "Please re-upload catalog configuration with a supported connector.", - connector); throw new IllegalStateException( - "Unknown connector. Connector doesn't exist in the list of supported."); + String.format("Unsupported Connector: %s", connector.name())); } - return storageEngine; } - private void constructConnectors(List catalogs) throws URISyntaxException { + private void constructConnectors(List catalogs) { catalogMap = new HashMap<>(); for (CatalogMetadata catalog : catalogs) { - String catalogName = catalog.getName(); - StorageEngine storageEngine = createStorageEngine(catalog); - catalogMap.put(catalogName, - new Catalog(catalog.getName(), catalog.getConnector(), storageEngine)); + try { + String catalogName = catalog.getName(); + StorageEngine storageEngine = createStorageEngine(catalog); + catalogMap.put(catalogName, + new Catalog(catalog.getName(), catalog.getConnector(), storageEngine)); + } catch (Throwable e) { + LOG.error("Catalog : {} storage engine creation failed with the following message: {}", + catalog.getName(), e.getMessage(), e); + } } } @@ -160,32 +158,28 @@ private void validateCatalogs(List catalogs) { for (CatalogMetadata catalog : catalogs) { if (StringUtils.isEmpty(catalog.getName())) { - LOG.error("Found a catalog with no name. {}", catalog.toString()); throw new IllegalArgumentException( "Missing Name Field from a catalog. Name is a required parameter."); } if (!catalog.getName().matches(CATALOG_NAME_REGEX)) { - LOG.error(String.format("Catalog Name: %s contains illegal characters." - + " Allowed characters: a-zA-Z0-9_-*@ ", catalog.getName())); throw new IllegalArgumentException( String.format("Catalog Name: %s contains illegal characters." + " Allowed characters: a-zA-Z0-9_-*@ ", catalog.getName())); } - if (StringUtils.isEmpty(catalog.getUri())) { - LOG.error("Found a catalog with no uri. {}", catalog.toString()); - throw new IllegalArgumentException( - "Missing URI Field from a catalog. URI is a required parameter."); - } - String catalogName = catalog.getName(); if (reviewedCatalogs.contains(catalogName)) { - LOG.error("Found duplicate catalog names"); throw new IllegalArgumentException("Catalogs with same name are not allowed."); } else { reviewedCatalogs.add(catalogName); } + + if (Objects.isNull(catalog.getProperties())) { + throw new IllegalArgumentException("Missing properties field in catalog configuration. " + + "Properties are required parameters"); + } + } } diff --git a/plugin/src/main/plugin-metadata/plugin-security.policy b/plugin/src/main/plugin-metadata/plugin-security.policy index 2dda426dc1..aec517aa84 100644 --- a/plugin/src/main/plugin-metadata/plugin-security.policy +++ b/plugin/src/main/plugin-metadata/plugin-security.policy @@ -9,6 +9,7 @@ grant { permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "defineClass"; permission java.lang.RuntimePermission "getClassLoader"; + permission java.lang.RuntimePermission "accessUserInformation"; permission java.net.NetPermission "getProxySelector"; permission java.net.SocketPermission "*", "accept,connect,resolve"; diff --git a/plugin/src/test/java/org/opensearch/sql/plugin/catalog/CatalogServiceImplTest.java b/plugin/src/test/java/org/opensearch/sql/plugin/catalog/CatalogServiceImplTest.java index 4d339f384b..07ee458e5c 100644 --- a/plugin/src/test/java/org/opensearch/sql/plugin/catalog/CatalogServiceImplTest.java +++ b/plugin/src/test/java/org/opensearch/sql/plugin/catalog/CatalogServiceImplTest.java @@ -63,28 +63,27 @@ public void testLoadConnectorsWithMultipleCatalogs() { @Test public void testLoadConnectorsWithMissingName() { Settings settings = getCatalogSettings("catalog_missing_name.json"); - IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, - () -> CatalogServiceImpl.getInstance().loadConnectors(settings)); - Assert.assertEquals("Missing Name Field from a catalog. Name is a required parameter.", - exception.getMessage()); + Set expected = CatalogServiceImpl.getInstance().getCatalogs(); + CatalogServiceImpl.getInstance().loadConnectors(settings); + Assert.assertEquals(expected, CatalogServiceImpl.getInstance().getCatalogs()); } @SneakyThrows @Test public void testLoadConnectorsWithDuplicateCatalogNames() { Settings settings = getCatalogSettings("duplicate_catalog_names.json"); - IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, - () -> CatalogServiceImpl.getInstance().loadConnectors(settings)); - Assert.assertEquals("Catalogs with same name are not allowed.", - exception.getMessage()); + Set expected = CatalogServiceImpl.getInstance().getCatalogs(); + CatalogServiceImpl.getInstance().loadConnectors(settings); + Assert.assertEquals(expected, CatalogServiceImpl.getInstance().getCatalogs()); } @SneakyThrows @Test public void testLoadConnectorsWithMalformedJson() { Settings settings = getCatalogSettings("malformed_catalogs.json"); - Assert.assertThrows(IllegalArgumentException.class, - () -> CatalogServiceImpl.getInstance().loadConnectors(settings)); + Set expected = CatalogServiceImpl.getInstance().getCatalogs(); + CatalogServiceImpl.getInstance().loadConnectors(settings); + Assert.assertEquals(expected, CatalogServiceImpl.getInstance().getCatalogs()); } @SneakyThrows @@ -124,10 +123,9 @@ public void testGetStorageEngineAfterLoadingConnectors() { @Test public void testLoadConnectorsWithIllegalCatalogNames() { Settings settings = getCatalogSettings("illegal_catalog_name.json"); - IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, - () -> CatalogServiceImpl.getInstance().loadConnectors(settings)); - Assert.assertEquals("Catalog Name: prometheus.test contains illegal characters." - + " Allowed characters: a-zA-Z0-9_-*@ ", exception.getMessage()); + Set expected = CatalogServiceImpl.getInstance().getCatalogs(); + CatalogServiceImpl.getInstance().loadConnectors(settings); + Assert.assertEquals(expected, CatalogServiceImpl.getInstance().getCatalogs()); } private Settings getCatalogSettings(String filename) throws URISyntaxException, IOException { diff --git a/plugin/src/test/resources/catalog_missing_name.json b/plugin/src/test/resources/catalog_missing_name.json index 86dc752cf0..4491ebb0db 100644 --- a/plugin/src/test/resources/catalog_missing_name.json +++ b/plugin/src/test/resources/catalog_missing_name.json @@ -1,11 +1,11 @@ [ { "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "type" } } ] \ No newline at end of file diff --git a/plugin/src/test/resources/catalogs.json b/plugin/src/test/resources/catalogs.json index aae3403462..5756b05094 100644 --- a/plugin/src/test/resources/catalogs.json +++ b/plugin/src/test/resources/catalogs.json @@ -2,11 +2,11 @@ { "name" : "prometheus", "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "type" } } ] \ No newline at end of file diff --git a/plugin/src/test/resources/duplicate_catalog_names.json b/plugin/src/test/resources/duplicate_catalog_names.json index b2f3694e5c..eefc56b6ef 100644 --- a/plugin/src/test/resources/duplicate_catalog_names.json +++ b/plugin/src/test/resources/duplicate_catalog_names.json @@ -2,21 +2,21 @@ { "name" : "prometheus", "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "type" } }, { "name" : "prometheus", "connector": "prometheus", - "uri" : "http://localhost:9219", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "type" } } ] \ No newline at end of file diff --git a/plugin/src/test/resources/illegal_catalog_name.json b/plugin/src/test/resources/illegal_catalog_name.json index 359bbcd712..212ca6ec93 100644 --- a/plugin/src/test/resources/illegal_catalog_name.json +++ b/plugin/src/test/resources/illegal_catalog_name.json @@ -2,11 +2,11 @@ { "name" : "prometheus.test", "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "type" } } ] \ No newline at end of file diff --git a/plugin/src/test/resources/multiple_catalogs.json b/plugin/src/test/resources/multiple_catalogs.json index 112ecad858..4dae501561 100644 --- a/plugin/src/test/resources/multiple_catalogs.json +++ b/plugin/src/test/resources/multiple_catalogs.json @@ -2,21 +2,22 @@ { "name" : "prometheus", "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "basicauth", + "prometheus.auth.username" : "admin", + "prometheus.auth.password" : "type" } }, { "name" : "prometheus-1", "connector": "prometheus", - "uri" : "http://localhost:9090", - "authentication" : { - "type" : "basicauth", - "username" : "admin", - "password" : "password" + "properties" : { + "prometheus.uri" : "http://localhost:9090", + "prometheus.auth.type" : "awssigv4", + "prometheus.auth.region" : "us-east-1", + "prometheus.auth.access_key" : "accessKey", + "prometheus.auth.secret_key" : "secretKey" } } ] \ No newline at end of file diff --git a/prometheus/build.gradle b/prometheus/build.gradle index c936e63785..45a3a4a8ed 100644 --- a/prometheus/build.gradle +++ b/prometheus/build.gradle @@ -9,23 +9,25 @@ plugins { id 'jacoco' } +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} + dependencies { api project(':core') - api group: 'org.opensearch', name: 'opensearch', version: "${opensearch_version}" implementation "io.github.resilience4j:resilience4j-retry:1.5.0" implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: "${jackson_version}" implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jackson_databind_version}" implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-cbor', version: "${jackson_version}" - compileOnly group: 'org.opensearch.client', name: 'opensearch-rest-high-level-client', version: "${opensearch_version}" - api group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3' + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3' + implementation 'com.github.babbel:okhttp-aws-signer:1.0.2' implementation group: 'org.json', name: 'json', version: '20180813' testImplementation('org.junit.jupiter:junit-jupiter:5.6.2') testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '2.1' testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.12.4' testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.12.4' - testImplementation group: 'org.opensearch.client', name: 'opensearch-rest-high-level-client', version: "${opensearch_version}" - testImplementation group: 'org.opensearch.test', name: 'framework', version: "${opensearch_version}" testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: '4.9.3' } diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptor.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptor.java new file mode 100644 index 0000000000..f3d91c55a2 --- /dev/null +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptor.java @@ -0,0 +1,59 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.prometheus.authinterceptors; + +import com.babbel.mobile.android.commons.okhttpawssigner.OkHttpAwsV4Signer; +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import lombok.NonNull; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class AwsSigningInterceptor implements Interceptor { + + private OkHttpAwsV4Signer okHttpAwsV4Signer; + + private String accessKey; + + private String secretKey; + + /** + * AwsSigningInterceptor which intercepts http requests + * and adds required headers for sigv4 authentication. + * + * @param accessKey accessKey. + * @param secretKey secretKey. + * @param region region. + * @param serviceName serviceName. + */ + public AwsSigningInterceptor(@NonNull String accessKey, @NonNull String secretKey, + @NonNull String region, @NonNull String serviceName) { + this.okHttpAwsV4Signer = new OkHttpAwsV4Signer(region, serviceName); + this.accessKey = accessKey; + this.secretKey = secretKey; + } + + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + Request request = chain.request(); + + DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'") + .withZone(ZoneId.of("GMT")); + + Request newRequest = request.newBuilder() + .addHeader("x-amz-date", timestampFormat.format(ZonedDateTime.now())) + .addHeader("host", request.url().host()) + .build(); + Request signed = okHttpAwsV4Signer.sign(newRequest, accessKey, secretKey); + return chain.proceed(signed); + } + +} diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptor.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptor.java new file mode 100644 index 0000000000..6151018567 --- /dev/null +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptor.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.prometheus.authinterceptors; + +import java.io.IOException; +import lombok.NonNull; +import okhttp3.Credentials; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class BasicAuthenticationInterceptor implements Interceptor { + + private String credentials; + + public BasicAuthenticationInterceptor(@NonNull String username, @NonNull String password) { + this.credentials = Credentials.basic(username, password); + } + + + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + Request request = chain.request(); + Request authenticatedRequest = request.newBuilder() + .header("Authorization", credentials).build(); + return chain.proceed(authenticatedRequest); + } + +} diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java index 1c427cd3a7..7068b848ca 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,7 +42,8 @@ public PrometheusClientImpl(OkHttpClient okHttpClient, URI uri) { @Override public JSONObject queryRange(String query, Long start, Long end, String step) throws IOException { String queryUrl = String.format("%s/api/v1/query_range?query=%s&start=%s&end=%s&step=%s", - uri.toString().replaceAll("/$", ""), query, start, end, step); + uri.toString().replaceAll("/$", ""), URLEncoder.encode(query, StandardCharsets.UTF_8), + start, end, step); logger.debug("queryUrl: " + queryUrl); Request request = new Request.Builder() .url(queryUrl) @@ -106,4 +109,4 @@ private JSONObject readResponse(Response response) throws IOException { } -} +} \ No newline at end of file diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/request/PrometheusQueryRequest.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/request/PrometheusQueryRequest.java index 3deb41569e..5d4bf2ae7c 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/request/PrometheusQueryRequest.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/request/PrometheusQueryRequest.java @@ -11,7 +11,6 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.opensearch.common.unit.TimeValue; /** * Prometheus metric query request. @@ -22,8 +21,6 @@ @AllArgsConstructor public class PrometheusQueryRequest { - public static final TimeValue DEFAULT_QUERY_TIMEOUT = TimeValue.timeValueMinutes(1L); - /** * PromQL. */ diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactory.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactory.java new file mode 100644 index 0000000000..41cbf3748f --- /dev/null +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactory.java @@ -0,0 +1,95 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.prometheus.storage; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import okhttp3.OkHttpClient; +import org.opensearch.sql.catalog.model.ConnectorType; +import org.opensearch.sql.catalog.model.auth.AuthenticationType; +import org.opensearch.sql.prometheus.authinterceptors.AwsSigningInterceptor; +import org.opensearch.sql.prometheus.authinterceptors.BasicAuthenticationInterceptor; +import org.opensearch.sql.prometheus.client.PrometheusClient; +import org.opensearch.sql.prometheus.client.PrometheusClientImpl; +import org.opensearch.sql.storage.StorageEngine; +import org.opensearch.sql.storage.StorageEngineFactory; + +public class PrometheusStorageFactory implements StorageEngineFactory { + + public static final String URI = "prometheus.uri"; + public static final String AUTH_TYPE = "prometheus.auth.type"; + public static final String USERNAME = "prometheus.auth.username"; + public static final String PASSWORD = "prometheus.auth.password"; + public static final String REGION = "prometheus.auth.region"; + public static final String ACCESS_KEY = "prometheus.auth.access_key"; + public static final String SECRET_KEY = "prometheus.auth.secret_key"; + + + @Override + public ConnectorType getConnectorType() { + return ConnectorType.PROMETHEUS; + } + + @Override + public StorageEngine getStorageEngine(String catalogName, Map requiredConfig) { + validateFieldsInConfig(requiredConfig, Set.of(URI)); + PrometheusClient prometheusClient; + try { + prometheusClient = new PrometheusClientImpl(getHttpClient(requiredConfig), + new URI(requiredConfig.get(URI))); + } catch (URISyntaxException e) { + throw new RuntimeException( + String.format("Prometheus Client creation failed due to: %s", e.getMessage())); + } + return new PrometheusStorageEngine(prometheusClient); + } + + + private OkHttpClient getHttpClient(Map config) { + OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder(); + okHttpClient.callTimeout(1, TimeUnit.MINUTES); + okHttpClient.connectTimeout(30, TimeUnit.SECONDS); + if (config.get(AUTH_TYPE) != null) { + AuthenticationType authenticationType = AuthenticationType.get(config.get(AUTH_TYPE)); + if (AuthenticationType.BASICAUTH.equals(authenticationType)) { + validateFieldsInConfig(config, Set.of(USERNAME, PASSWORD)); + okHttpClient.addInterceptor(new BasicAuthenticationInterceptor(config.get(USERNAME), + config.get(PASSWORD))); + } else if (AuthenticationType.AWSSIGV4AUTH.equals(authenticationType)) { + validateFieldsInConfig(config, Set.of(REGION, ACCESS_KEY, SECRET_KEY)); + okHttpClient.addInterceptor(new AwsSigningInterceptor( + config.get(ACCESS_KEY), config.get(SECRET_KEY), + config.get(REGION), "aps")); + } else { + throw new IllegalArgumentException( + String.format("AUTH Type : %s is not supported with Prometheus Connector", + config.get(AUTH_TYPE))); + } + } + return okHttpClient.build(); + } + + private void validateFieldsInConfig(Map config, Set fields) { + Set missingFields = new HashSet<>(); + for (String field : fields) { + if (!config.containsKey(field)) { + missingFields.add(field); + } + } + if (missingFields.size() > 0) { + throw new IllegalArgumentException(String.format( + "Missing %s fields in the Prometheus connector properties.", missingFields)); + } + } + + +} diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptorTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptorTest.java new file mode 100644 index 0000000000..a9224bf80f --- /dev/null +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/AwsSigningInterceptorTest.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.prometheus.authinterceptors; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import lombok.SneakyThrows; +import okhttp3.Interceptor; +import okhttp3.Request; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class AwsSigningInterceptorTest { + + @Mock + private Interceptor.Chain chain; + + @Captor + ArgumentCaptor requestArgumentCaptor; + + @Test + void testConstructors() { + Assertions.assertThrows(NullPointerException.class, () -> + new AwsSigningInterceptor(null, "secretKey", "us-east-1", "aps")); + Assertions.assertThrows(NullPointerException.class, () -> + new AwsSigningInterceptor("accessKey", null, "us-east-1", "aps")); + Assertions.assertThrows(NullPointerException.class, () -> + new AwsSigningInterceptor("accessKey", "secretKey", null, "aps")); + Assertions.assertThrows(NullPointerException.class, () -> + new AwsSigningInterceptor("accessKey", "secretKey", "us-east-1", null)); + } + + @Test + @SneakyThrows + void testIntercept() { + when(chain.request()).thenReturn(new Request.Builder() + .url("http://localhost:9090") + .build()); + AwsSigningInterceptor awsSigningInterceptor + = new AwsSigningInterceptor("testAccessKey", "testSecretKey", "us-east-1", "aps"); + awsSigningInterceptor.intercept(chain); + verify(chain).proceed(requestArgumentCaptor.capture()); + Request request = requestArgumentCaptor.getValue(); + Assertions.assertNotNull(request.headers("Authorization")); + Assertions.assertNotNull(request.headers("x-amz-date")); + Assertions.assertNotNull(request.headers("host")); + } + +} diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptorTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptorTest.java new file mode 100644 index 0000000000..b5b5acd457 --- /dev/null +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/authinterceptors/BasicAuthenticationInterceptorTest.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.prometheus.authinterceptors; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import lombok.SneakyThrows; +import okhttp3.Credentials; +import okhttp3.Interceptor; +import okhttp3.Request; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.internal.matchers.Null; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class BasicAuthenticationInterceptorTest { + + @Mock + private Interceptor.Chain chain; + + @Captor + ArgumentCaptor requestArgumentCaptor; + + @Test + void testConstructors() { + Assertions.assertThrows(NullPointerException.class, () -> + new BasicAuthenticationInterceptor(null, "test")); + Assertions.assertThrows(NullPointerException.class, () -> + new BasicAuthenticationInterceptor("testAdmin", null)); + } + + + @Test + @SneakyThrows + void testIntercept() { + when(chain.request()).thenReturn(new Request.Builder() + .url("http://localhost:9090") + .build()); + BasicAuthenticationInterceptor basicAuthenticationInterceptor + = new BasicAuthenticationInterceptor("testAdmin", "testPassword"); + basicAuthenticationInterceptor.intercept(chain); + verify(chain).proceed(requestArgumentCaptor.capture()); + Request request = requestArgumentCaptor.getValue(); + Assertions.assertEquals( + Collections.singletonList(Credentials.basic("testAdmin", "testPassword")), + request.headers("Authorization")); + } + +} diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java index f49672a882..76abb05751 100644 --- a/prometheus/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java @@ -28,7 +28,6 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.apache.http.HttpStatus; import org.json.JSONObject; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -86,7 +85,7 @@ void testQueryRangeWith2xxStatusAndError() { void testQueryRangeWithNon2xxError() { MockResponse mockResponse = new MockResponse() .addHeader("Content-Type", "application/json; charset=utf-8") - .setResponseCode(HttpStatus.SC_BAD_REQUEST); + .setResponseCode(400); mockWebServer.enqueue(mockResponse); RuntimeException runtimeException = assertThrows(RuntimeException.class, diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactoryTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactoryTest.java new file mode 100644 index 0000000000..1b54cde5d9 --- /dev/null +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusStorageFactoryTest.java @@ -0,0 +1,135 @@ +/* + * + * * Copyright OpenSearch Contributors + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.sql.prometheus.storage; + +import java.util.HashMap; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.sql.catalog.model.ConnectorType; +import org.opensearch.sql.storage.StorageEngine; + +@ExtendWith(MockitoExtension.class) +public class PrometheusStorageFactoryTest { + + @Test + void testGetConnectorType() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + Assertions.assertEquals(ConnectorType.PROMETHEUS, prometheusStorageFactory.getConnectorType()); + } + + @Test + @SneakyThrows + void testGetStorageEngineWithBasicAuth() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.uri", "http://dummyprometheus:9090"); + properties.put("prometheus.auth.type", "basicauth"); + properties.put("prometheus.auth.username", "admin"); + properties.put("prometheus.auth.password", "admin"); + StorageEngine storageEngine + = prometheusStorageFactory.getStorageEngine("my_prometheus", properties); + Assertions.assertTrue(storageEngine instanceof PrometheusStorageEngine); + } + + @Test + @SneakyThrows + void testGetStorageEngineWithAWSSigV4Auth() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.uri", "http://dummyprometheus:9090"); + properties.put("prometheus.auth.type", "awssigv4"); + properties.put("prometheus.auth.region", "us-east-1"); + properties.put("prometheus.auth.secret_key", "accessKey"); + properties.put("prometheus.auth.access_key", "secretKey"); + StorageEngine storageEngine + = prometheusStorageFactory.getStorageEngine("my_prometheus", properties); + Assertions.assertTrue(storageEngine instanceof PrometheusStorageEngine); + } + + + @Test + @SneakyThrows + void testGetStorageEngineWithMissingURI() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.auth.type", "awssigv4"); + properties.put("prometheus.auth.region", "us-east-1"); + properties.put("prometheus.auth.secret_key", "accessKey"); + properties.put("prometheus.auth.access_key", "secretKey"); + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> prometheusStorageFactory.getStorageEngine("my_prometheus", properties)); + Assertions.assertEquals("Missing [prometheus.uri] fields " + + "in the Prometheus connector properties.", + exception.getMessage()); + } + + @Test + @SneakyThrows + void testGetStorageEngineWithMissingRegionInAWS() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.uri", "http://dummyprometheus:9090"); + properties.put("prometheus.auth.type", "awssigv4"); + properties.put("prometheus.auth.secret_key", "accessKey"); + properties.put("prometheus.auth.access_key", "secretKey"); + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> prometheusStorageFactory.getStorageEngine("my_prometheus", properties)); + Assertions.assertEquals("Missing [prometheus.auth.region] fields in the " + + "Prometheus connector properties.", + exception.getMessage()); + } + + @Test + @SneakyThrows + void testGetStorageEngineWithWrongAuthType() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.uri", "https://test.com"); + properties.put("prometheus.auth.type", "random"); + properties.put("prometheus.auth.region", "us-east-1"); + properties.put("prometheus.auth.secret_key", "accessKey"); + properties.put("prometheus.auth.access_key", "secretKey"); + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> prometheusStorageFactory.getStorageEngine("my_prometheus", properties)); + Assertions.assertEquals("AUTH Type : random is not supported with Prometheus Connector", + exception.getMessage()); + } + + + @Test + @SneakyThrows + void testGetStorageEngineWithNONEAuthType() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.uri", "https://test.com"); + StorageEngine storageEngine + = prometheusStorageFactory.getStorageEngine("my_prometheus", properties); + Assertions.assertTrue(storageEngine instanceof PrometheusStorageEngine); + } + + @Test + @SneakyThrows + void testGetStorageEngineWithInvalidURISyntax() { + PrometheusStorageFactory prometheusStorageFactory = new PrometheusStorageFactory(); + HashMap properties = new HashMap<>(); + properties.put("prometheus.uri", "http://dummyprometheus:9090? param"); + properties.put("prometheus.auth.type", "basicauth"); + properties.put("prometheus.auth.username", "admin"); + properties.put("prometheus.auth.password", "admin"); + RuntimeException exception = Assertions.assertThrows(RuntimeException.class, + () -> prometheusStorageFactory.getStorageEngine("my_prometheus", properties)); + Assertions.assertTrue( + exception.getMessage().contains("Prometheus Client creation failed due to:")); + } + + +} +