From 5c4cae21f35977e52511860d0ff9ed0133c5fcc6 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 15 Aug 2023 10:23:05 +0530 Subject: [PATCH 01/20] Elasticache client --- pom.xml | 6 +++--- server/pom.xml | 4 ++-- .../org/apache/druid/client/cache/MemcachedCacheTest.java | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 5ce962778542..c664f05a761e 100644 --- a/pom.xml +++ b/pom.xml @@ -766,9 +766,9 @@ 3.3.6 - net.spy - spymemcached - 2.12.3 + com.amazonaws + elasticache-java-cluster-client + 1.2.0 org.antlr diff --git a/server/pom.xml b/server/pom.xml index 5ba0b170a9ed..6154ecfa4c20 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -134,8 +134,8 @@ tesla-aether - net.spy - spymemcached + com.amazonaws + elasticache-java-cluster-client org.lz4 diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index 4326e0e90988..83ed306aaa1b 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -316,6 +316,12 @@ public Collection getAvailableServers() throw new UnsupportedOperationException("not implemented"); } + @Override + public boolean refreshCertificate() + { + return true; + } + @Override public Collection getUnavailableServers() { From 1bbabc4c8a1406898542e88ede692a289fee12ba Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 16 Aug 2023 11:53:13 +0530 Subject: [PATCH 02/20] Update license for aws elasticache client --- licenses.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/licenses.yaml b/licenses.yaml index 23ff4ee754e2..828c16487272 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -1638,13 +1638,13 @@ libraries: --- -name: Spymemcached +name: aws-elasticache-cluster-client-memcached-for-java license_category: binary module: java-core license_name: Apache License version 2.0 -version: 2.12.3 +version: 1.2.0 libraries: - - net.spy: spymemcached + - com.amazonaws: elasticache-java-cluster-client --- From 52dba0a8aba9822f92636fbd217de754087c87c5 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Mon, 21 Aug 2023 21:53:18 +0530 Subject: [PATCH 03/20] Add sslconnection and autodiscovery configs --- docs/configuration/index.md | 22 ++++++----- .../druid/client/cache/MemcachedCache.java | 37 ++++++++++++++++--- .../client/cache/MemcachedCacheConfig.java | 14 +++++++ .../client/cache/MemcachedCacheTest.java | 29 +++++++++++---- 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 753045a92a90..838869f7e2d8 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2103,16 +2103,18 @@ In addition to the normal cache metrics, the caffeine cache implementation also Uses memcached as cache backend. This allows all processes to share the same cache. -|Property|Description|Default| -|--------|-----------|-------| -|`druid.cache.expiration`|Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol).|2592000 (30 days)| -|`druid.cache.timeout`|Maximum time in milliseconds to wait for a response from Memcached.|500| -|`druid.cache.hosts`|Comma separated list of Memcached hosts ``.|none| -|`druid.cache.maxObjectSize`|Maximum object size in bytes for a Memcached object.|52428800 (50 MiB)| -|`druid.cache.memcachedPrefix`|Key prefix for all keys in Memcached.|druid| -|`druid.cache.numConnections`|Number of memcached connections to use.|1| -|`druid.cache.protocol`|Memcached communication protocol. Can be binary or text.|binary| -|`druid.cache.locator`|Memcached locator. Can be consistent or array_mod.|consistent| +| Property | Description | Default | +|-------------------------------|------------------------------------------------------------------------------------------------------|-------------------| +| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | +| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. | none | +| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | +| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | +| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | +| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | +| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | +| `druid.cache.sslconnection` | Use TLS based ssl connection for Memcached client. Boolean | false | +| `druid.cache.autodiscovery` | Use AutoDiscovery feature of AWS Elasticache Memcached. Boolean | false | #### Hybrid diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index c13ca5434420..e4bb2ca4c045 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -29,6 +29,12 @@ import com.google.common.collect.Maps; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; +import net.spy.memcached.*; + +import java.security.KeyManagementException; +import java.security.KeyStore; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; import net.spy.memcached.AddrUtil; import net.spy.memcached.ConnectionFactory; import net.spy.memcached.ConnectionFactoryBuilder; @@ -52,10 +58,13 @@ import org.apache.druid.java.util.metrics.AbstractMonitor; import javax.annotation.Nullable; +import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -339,7 +348,7 @@ public void updateHistogram(String name, int amount) } }; - final ConnectionFactory connectionFactory = new MemcachedCustomConnectionFactoryBuilder() + ConnectionFactoryBuilder connectionFactoryBuilder = new MemcachedCustomConnectionFactoryBuilder() // 1000 repetitions gives us good distribution with murmur3_128 // (approx < 5% difference in counts across nodes, with 5 cache nodes) .setKetamaNodeRepetitions(1000) @@ -355,9 +364,23 @@ public void updateHistogram(String name, int amount) .setReadBufferSize(config.getReadBufferSize()) .setOpQueueFactory(opQueueFactory) .setMetricCollector(metricCollector) - .setEnableMetrics(MetricType.DEBUG) // Not as scary as it sounds - .build(); - + .setEnableMetrics(MetricType.DEBUG); // Not as scary as it sounds + if(config.usesslconnection()) { + // Build SSLContext + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + // Create the client in TLS mode + connectionFactoryBuilder.setSSLContext(sslContext); + } + if(config.autoDiscovery()) { + connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); + } + else { + connectionFactoryBuilder.setClientMode(ClientMode.Static); + } + final ConnectionFactory connectionFactory = connectionFactoryBuilder.build(); final List hosts = AddrUtil.getAddresses(config.getHosts()); @@ -389,7 +412,11 @@ public MemcachedClientIF get() return new MemcachedCache(clientSupplier, config, monitor); } - catch (IOException e) { + catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (KeyStoreException e) { + throw new RuntimeException(e); + } catch (KeyManagementException e) { throw new RuntimeException(e); } } diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java index 6ad250396a04..0ba7fe5428f0 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java @@ -63,6 +63,12 @@ public class MemcachedCacheConfig @JsonProperty private String locator = "consistent"; + @JsonProperty + private boolean sslconnection = false; + + @JsonProperty + private boolean autodiscovery = false; + public int getExpiration() { return expiration; @@ -112,4 +118,12 @@ public String getLocator() { return locator; } + + public boolean usesslconnection() { + return sslconnection; + } + + public boolean autoDiscovery() { + return autodiscovery; + } } diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index 83ed306aaa1b..dfa85df89150 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -29,14 +29,7 @@ import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.name.Names; -import net.spy.memcached.BroadcastOpFactory; -import net.spy.memcached.CASResponse; -import net.spy.memcached.CASValue; -import net.spy.memcached.CachedData; -import net.spy.memcached.ConnectionObserver; -import net.spy.memcached.MemcachedClientIF; -import net.spy.memcached.MemcachedNode; -import net.spy.memcached.NodeLocator; +import net.spy.memcached.*; import net.spy.memcached.internal.BulkFuture; import net.spy.memcached.internal.BulkGetCompletionListener; import net.spy.memcached.internal.OperationFuture; @@ -224,6 +217,26 @@ public void emit(Event event) } } + @Test + public void testSslConnection() + { + final MemcachedCacheConfig config = new MemcachedCacheConfig() + { + @Override + public boolean usesslconnection() + { + return true; + } + + @Override + public String getHosts() + { + return "localhost:9999"; + } + }; + MemcachedCache cache = MemcachedCache.create(config); + } + @Test public void testSanity() { From 9deeb1d2d1153bfd69b0d1dd04c4fdc46b67a7d3 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 22 Aug 2023 10:14:04 +0530 Subject: [PATCH 04/20] PR comments --- docs/configuration/index.md | 24 +++++++++---------- .../druid/client/cache/MemcachedCache.java | 10 +++----- .../client/cache/MemcachedCacheConfig.java | 12 +++++----- .../client/cache/MemcachedCacheTest.java | 4 ++-- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 838869f7e2d8..bdd79a342081 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2103,18 +2103,18 @@ In addition to the normal cache metrics, the caffeine cache implementation also Uses memcached as cache backend. This allows all processes to share the same cache. -| Property | Description | Default | -|-------------------------------|------------------------------------------------------------------------------------------------------|-------------------| -| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | -| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | -| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. | none | -| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | -| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | -| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | -| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | -| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | -| `druid.cache.sslconnection` | Use TLS based ssl connection for Memcached client. Boolean | false | -| `druid.cache.autodiscovery` | Use AutoDiscovery feature of AWS Elasticache Memcached. Boolean | false | +| Property | Description | Default | +|-------------------------------|----------------------------------------------------------------------------------------------------------------|-------------------| +| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | +| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. | none | +| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | +| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | +| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | +| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | +| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | +| `druid.cache.enableTls` | Use TLS based ssl connection for Memcached client. Boolean | false | +| `druid.cache.clientMode` | ClientMode. Dynamic mode uses AutoDiscovery feature of AWS Elasticache Memcache. String. "static" or "dynamic" | static | #### Hybrid diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index e4bb2ca4c045..1c62e13d4019 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -65,11 +65,7 @@ import java.nio.charset.StandardCharsets; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; @@ -365,7 +361,7 @@ public void updateHistogram(String name, int amount) .setOpQueueFactory(opQueueFactory) .setMetricCollector(metricCollector) .setEnableMetrics(MetricType.DEBUG); // Not as scary as it sounds - if(config.usesslconnection()) { + if(config.enableTls()) { // Build SSLContext TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); @@ -374,7 +370,7 @@ public void updateHistogram(String name, int amount) // Create the client in TLS mode connectionFactoryBuilder.setSSLContext(sslContext); } - if(config.autoDiscovery()) { + if(Objects.equals(config.getClientMode(), "dynamic")) { connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); } else { diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java index 0ba7fe5428f0..bdaab9c203e3 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java @@ -64,10 +64,10 @@ public class MemcachedCacheConfig private String locator = "consistent"; @JsonProperty - private boolean sslconnection = false; + private boolean enableTls = false; @JsonProperty - private boolean autodiscovery = false; + private String clientMode = "static"; public int getExpiration() { @@ -119,11 +119,11 @@ public String getLocator() return locator; } - public boolean usesslconnection() { - return sslconnection; + public boolean enableTls() { + return enableTls; } - public boolean autoDiscovery() { - return autodiscovery; + public String getClientMode() { + return clientMode; } } diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index dfa85df89150..c2f74475188c 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -223,7 +223,7 @@ public void testSslConnection() final MemcachedCacheConfig config = new MemcachedCacheConfig() { @Override - public boolean usesslconnection() + public boolean enableTls() { return true; } @@ -234,7 +234,7 @@ public String getHosts() return "localhost:9999"; } }; - MemcachedCache cache = MemcachedCache.create(config); + MemcachedCache.create(config); } @Test From d72e3c1c296478727c070263dab528e1ee978f12 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 23 Aug 2023 15:48:50 +0530 Subject: [PATCH 05/20] Modified description --- docs/configuration/index.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index bdd79a342081..95289713b599 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2103,18 +2103,18 @@ In addition to the normal cache metrics, the caffeine cache implementation also Uses memcached as cache backend. This allows all processes to share the same cache. -| Property | Description | Default | -|-------------------------------|----------------------------------------------------------------------------------------------------------------|-------------------| -| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | -| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | -| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. | none | -| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | -| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | -| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | -| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | -| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | -| `druid.cache.enableTls` | Use TLS based ssl connection for Memcached client. Boolean | false | -| `druid.cache.clientMode` | ClientMode. Dynamic mode uses AutoDiscovery feature of AWS Elasticache Memcache. String. "static" or "dynamic" | static | +| Property | Description | Default | +|-------------------------------|--------------------------------------------------------------------------------------------------------|-------------------| +| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | +| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. | none | +| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | +| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | +| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | +| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | +| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | +| `druid.cache.enableTls` | Use TLS based ssl connection for Memcached client. Boolean | false | +| `druid.cache.clientMode` | Client Mode. Dynamic mode uses AutoDiscovery feature of AWS Memcached. String. "static" or "dynamic" | static | #### Hybrid From 2a1e535fd912b5182cf52d67fb717a0bcc0651e4 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Fri, 25 Aug 2023 10:08:07 +0530 Subject: [PATCH 06/20] Update documentation as per PR review --- docs/configuration/index.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 95289713b599..556e5e513b9f 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2103,18 +2103,18 @@ In addition to the normal cache metrics, the caffeine cache implementation also Uses memcached as cache backend. This allows all processes to share the same cache. -| Property | Description | Default | -|-------------------------------|--------------------------------------------------------------------------------------------------------|-------------------| -| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | -| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | -| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. | none | -| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | -| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | -| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | -| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | -| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | -| `druid.cache.enableTls` | Use TLS based ssl connection for Memcached client. Boolean | false | -| `druid.cache.clientMode` | Client Mode. Dynamic mode uses AutoDiscovery feature of AWS Memcached. String. "static" or "dynamic" | static | +| Property | Description | Default | +|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | +| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Needed only when druid.cache.clientMode=static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) | none | +| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | +| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | +| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | +| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | +| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | +| `druid.cache.enableTls` | Use TLS based ssl connection for Memcached client. Boolean | false | +| `druid.cache.clientMode` | Client Mode. Static mode requires the user to specify individual cluster nodes. Dynamic mode uses [AutoDiscovery](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.HowAutoDiscoveryWorks.html) feature of AWS Memcached. String. ["static"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Manual.html) or ["dynamic"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Using.ModifyApp.Java.html) | static | #### Hybrid From 10916ba821b1ac975fc51cf768cac53d37d593c4 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Mon, 28 Aug 2023 12:56:19 +0530 Subject: [PATCH 07/20] Updated docs --- docs/configuration/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 556e5e513b9f..27fcf93527c1 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2107,13 +2107,13 @@ Uses memcached as cache backend. This allows all processes to share the same cac |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| | `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | | `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | -| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Needed only when druid.cache.clientMode=static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) | none | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Need to specify all nodes when druid.cache.clientMode=static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) so just specifying the config endpoint and port is fine. | none | | `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | | `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | | `druid.cache.numConnections` | Number of memcached connections to use. | 1 | | `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | | `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | -| `druid.cache.enableTls` | Use TLS based ssl connection for Memcached client. Boolean | false | +| `druid.cache.enableTls` | Enable TLS based connection for Memcached client. Boolean | false | | `druid.cache.clientMode` | Client Mode. Static mode requires the user to specify individual cluster nodes. Dynamic mode uses [AutoDiscovery](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.HowAutoDiscoveryWorks.html) feature of AWS Memcached. String. ["static"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Manual.html) or ["dynamic"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Using.ModifyApp.Java.html) | static | #### Hybrid From 4889f7e08bd5112bbdea4439ebafc263cb0665ba Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 29 Aug 2023 12:22:34 +0530 Subject: [PATCH 08/20] Fix lint error --- docs/configuration/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 27fcf93527c1..498e911441e5 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2107,7 +2107,7 @@ Uses memcached as cache backend. This allows all processes to share the same cac |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| | `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | | `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | -| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Need to specify all nodes when druid.cache.clientMode=static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) so just specifying the config endpoint and port is fine. | none | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Need to specify all nodes when `druid.cache.clientMode` is set to static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) so just specifying the configuration endpoint and port is fine. | none | | `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | | `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | | `druid.cache.numConnections` | Number of memcached connections to use. | 1 | From 1bd5efedac20ca036a05035ae7f8236370a8cf30 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Mon, 4 Sep 2023 09:51:32 +0530 Subject: [PATCH 09/20] Add skipTlsHostnameVerification option --- docs/configuration/index.md | 25 ++++++++++--------- .../druid/client/cache/MemcachedCache.java | 6 ++++- .../client/cache/MemcachedCacheConfig.java | 7 ++++++ .../client/cache/MemcachedCacheTest.java | 13 +++++++++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 498e911441e5..fa3bc943bed8 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -2103,18 +2103,19 @@ In addition to the normal cache metrics, the caffeine cache implementation also Uses memcached as cache backend. This allows all processes to share the same cache. -| Property | Description | Default | -|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| -| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | -| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | -| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Need to specify all nodes when `druid.cache.clientMode` is set to static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) so just specifying the configuration endpoint and port is fine. | none | -| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | -| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | -| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | -| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | -| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | -| `druid.cache.enableTls` | Enable TLS based connection for Memcached client. Boolean | false | -| `druid.cache.clientMode` | Client Mode. Static mode requires the user to specify individual cluster nodes. Dynamic mode uses [AutoDiscovery](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.HowAutoDiscoveryWorks.html) feature of AWS Memcached. String. ["static"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Manual.html) or ["dynamic"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Using.ModifyApp.Java.html) | static | +| Property | Description | Default | +|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| `druid.cache.expiration` | Memcached [expiration time](https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol). | 2592000 (30 days) | +| `druid.cache.timeout` | Maximum time in milliseconds to wait for a response from Memcached. | 500 | +| `druid.cache.hosts` | Comma separated list of Memcached hosts ``. Need to specify all nodes when `druid.cache.clientMode` is set to static. Dynamic mode [automatically identifies nodes in your cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.html) so just specifying the configuration endpoint and port is fine. | none | +| `druid.cache.maxObjectSize` | Maximum object size in bytes for a Memcached object. | 52428800 (50 MiB) | +| `druid.cache.memcachedPrefix` | Key prefix for all keys in Memcached. | druid | +| `druid.cache.numConnections` | Number of memcached connections to use. | 1 | +| `druid.cache.protocol` | Memcached communication protocol. Can be binary or text. | binary | +| `druid.cache.locator` | Memcached locator. Can be consistent or array_mod. | consistent | +| `druid.cache.enableTls` | Enable TLS based connection for Memcached client. Boolean | false | +| `druid.cache.clientMode` | Client Mode. Static mode requires the user to specify individual cluster nodes. Dynamic mode uses [AutoDiscovery](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.HowAutoDiscoveryWorks.html) feature of AWS Memcached. String. ["static"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Manual.html) or ["dynamic"](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/AutoDiscovery.Using.ModifyApp.Java.html) | static | +| `druid.cache.skipTlsHostnameVerification` | Skip TLS Hostname Verification. Boolean. | true | #### Hybrid diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index 1c62e13d4019..5911760c4f92 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -370,12 +370,16 @@ public void updateHistogram(String name, int amount) // Create the client in TLS mode connectionFactoryBuilder.setSSLContext(sslContext); } - if(Objects.equals(config.getClientMode(), "dynamic")) { + if("dynamic".equals(config.getClientMode())) { connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); + connectionFactoryBuilder.setHostnameForTlsVerification(config.getHosts().split(",")[0]); } else { connectionFactoryBuilder.setClientMode(ClientMode.Static); } + if(config.skipTlsHostnameVerification()) { + connectionFactoryBuilder.setSkipTlsHostnameVerification(true); + } final ConnectionFactory connectionFactory = connectionFactoryBuilder.build(); final List hosts = AddrUtil.getAddresses(config.getHosts()); diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java index bdaab9c203e3..d837b8734a23 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java @@ -69,6 +69,9 @@ public class MemcachedCacheConfig @JsonProperty private String clientMode = "static"; + @JsonProperty + private boolean skipTlsHostnameVerification = true ; + public int getExpiration() { return expiration; @@ -126,4 +129,8 @@ public boolean enableTls() { public String getClientMode() { return clientMode; } + + public boolean skipTlsHostnameVerification() { + return skipTlsHostnameVerification; + } } diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index c2f74475188c..2792c2354df8 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -234,7 +234,18 @@ public String getHosts() return "localhost:9999"; } }; - MemcachedCache.create(config); + // Static Mode + Assert.assertEquals(config.getClientMode(),"static"); + MemcachedCache client = new MemcachedCache( + Suppliers.ofInstance( + StupidResourceHolder.create(new MockMemcachedClient()) + ), + config, + NOOP_MONITOR + ); + Assert.assertNull(client.get(new Cache.NamedKey("a", HI))); + put(client, "a", HI, 1); + Assert.assertEquals(1, get(client, "a", HI)); } @Test From dd1097208737237e4d107df800f0cb7a10950693 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 5 Sep 2023 22:45:00 +0530 Subject: [PATCH 10/20] codestyle fixes --- .../druid/client/cache/MemcachedCache.java | 17 ++++++++--------- .../client/cache/MemcachedCacheConfig.java | 11 +++++++---- .../druid/client/cache/MemcachedCacheTest.java | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index 5911760c4f92..f7f7ba02da74 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -29,13 +29,12 @@ import com.google.common.collect.Maps; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; -import net.spy.memcached.*; - import java.security.KeyManagementException; import java.security.KeyStore; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import net.spy.memcached.AddrUtil; +import net.spy.memcached.ClientMode; import net.spy.memcached.ConnectionFactory; import net.spy.memcached.ConnectionFactoryBuilder; import net.spy.memcached.FailureMode; @@ -58,7 +57,6 @@ import org.apache.druid.java.util.metrics.AbstractMonitor; import javax.annotation.Nullable; -import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -361,7 +359,7 @@ public void updateHistogram(String name, int amount) .setOpQueueFactory(opQueueFactory) .setMetricCollector(metricCollector) .setEnableMetrics(MetricType.DEBUG); // Not as scary as it sounds - if(config.enableTls()) { + if (config.enableTls()) { // Build SSLContext TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); @@ -370,14 +368,13 @@ public void updateHistogram(String name, int amount) // Create the client in TLS mode connectionFactoryBuilder.setSSLContext(sslContext); } - if("dynamic".equals(config.getClientMode())) { + if ("dynamic".equals(config.getClientMode())) { connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); connectionFactoryBuilder.setHostnameForTlsVerification(config.getHosts().split(",")[0]); } else { connectionFactoryBuilder.setClientMode(ClientMode.Static); - } - if(config.skipTlsHostnameVerification()) { + } if (config.skipTlsHostnameVerification()) { connectionFactoryBuilder.setSkipTlsHostnameVerification(true); } final ConnectionFactory connectionFactory = connectionFactoryBuilder.build(); @@ -414,9 +411,11 @@ public MemcachedClientIF get() } catch (IOException | NoSuchAlgorithmException e) { throw new RuntimeException(e); - } catch (KeyStoreException e) { + } + catch (KeyStoreException e) { throw new RuntimeException(e); - } catch (KeyManagementException e) { + } + catch (KeyManagementException e) { throw new RuntimeException(e); } } diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java index d837b8734a23..338cdba33c1c 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCacheConfig.java @@ -70,7 +70,7 @@ public class MemcachedCacheConfig private String clientMode = "static"; @JsonProperty - private boolean skipTlsHostnameVerification = true ; + private boolean skipTlsHostnameVerification = true; public int getExpiration() { @@ -122,15 +122,18 @@ public String getLocator() return locator; } - public boolean enableTls() { + public boolean enableTls() + { return enableTls; } - public String getClientMode() { + public String getClientMode() + { return clientMode; } - public boolean skipTlsHostnameVerification() { + public boolean skipTlsHostnameVerification() + { return skipTlsHostnameVerification; } } diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index 2792c2354df8..3f20706ea40c 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -235,7 +235,7 @@ public String getHosts() } }; // Static Mode - Assert.assertEquals(config.getClientMode(),"static"); + Assert.assertEquals(config.getClientMode(), "static"); MemcachedCache client = new MemcachedCache( Suppliers.ofInstance( StupidResourceHolder.create(new MockMemcachedClient()) From e419b24d6c43ad21e7019118c183ad99b1126a31 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 5 Sep 2023 23:00:27 +0530 Subject: [PATCH 11/20] throw error on invalid client mode --- .../org/apache/druid/client/cache/MemcachedCache.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index f7f7ba02da74..fb91a1fc0045 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -371,11 +371,13 @@ public void updateHistogram(String name, int amount) if ("dynamic".equals(config.getClientMode())) { connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); connectionFactoryBuilder.setHostnameForTlsVerification(config.getHosts().split(",")[0]); - } - else { + } else if ("static".equals(config.getClientMode())) { connectionFactoryBuilder.setClientMode(ClientMode.Static); - } if (config.skipTlsHostnameVerification()) { - connectionFactoryBuilder.setSkipTlsHostnameVerification(true); + } else { + throw new RuntimeException("Invalid value provided for `druid.cache.clientMode`. Value must be 'static' or 'dynamic'."); + } + if (config.skipTlsHostnameVerification()) { + connectionFactoryBuilder.setSkipTlsHostnameVerification(true); } final ConnectionFactory connectionFactory = connectionFactoryBuilder.build(); final List hosts = AddrUtil.getAddresses(config.getHosts()); From 16ada095639a81ab9404ec943614e21e82311a5a Mon Sep 17 00:00:00 2001 From: pagrawal Date: Thu, 7 Sep 2023 10:46:55 +0530 Subject: [PATCH 12/20] Refactor connectionFactoryBuilder --- .../druid/client/cache/MemcachedCache.java | 79 ++++++++++--------- ...mcachedCustomConnectionFactoryBuilder.java | 2 +- .../client/cache/MemcachedCacheTest.java | 32 ++++---- 3 files changed, 58 insertions(+), 55 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index fb91a1fc0045..ee148078936b 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -342,44 +342,8 @@ public void updateHistogram(String name, int amount) } }; - ConnectionFactoryBuilder connectionFactoryBuilder = new MemcachedCustomConnectionFactoryBuilder() - // 1000 repetitions gives us good distribution with murmur3_128 - // (approx < 5% difference in counts across nodes, with 5 cache nodes) - .setKetamaNodeRepetitions(1000) - .setHashAlg(MURMUR3_128) - .setProtocol(ConnectionFactoryBuilder.Protocol.valueOf(StringUtils.toUpperCase(config.getProtocol()))) - .setLocatorType(ConnectionFactoryBuilder.Locator.valueOf(StringUtils.toUpperCase(config.getLocator()))) - .setDaemon(true) - .setFailureMode(FailureMode.Cancel) - .setTranscoder(transcoder) - .setShouldOptimize(true) - .setOpQueueMaxBlockTime(config.getTimeout()) - .setOpTimeout(config.getTimeout()) - .setReadBufferSize(config.getReadBufferSize()) - .setOpQueueFactory(opQueueFactory) - .setMetricCollector(metricCollector) - .setEnableMetrics(MetricType.DEBUG); // Not as scary as it sounds - if (config.enableTls()) { - // Build SSLContext - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init((KeyStore) null); - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tmf.getTrustManagers(), null); - // Create the client in TLS mode - connectionFactoryBuilder.setSSLContext(sslContext); - } - if ("dynamic".equals(config.getClientMode())) { - connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); - connectionFactoryBuilder.setHostnameForTlsVerification(config.getHosts().split(",")[0]); - } else if ("static".equals(config.getClientMode())) { - connectionFactoryBuilder.setClientMode(ClientMode.Static); - } else { - throw new RuntimeException("Invalid value provided for `druid.cache.clientMode`. Value must be 'static' or 'dynamic'."); - } - if (config.skipTlsHostnameVerification()) { - connectionFactoryBuilder.setSkipTlsHostnameVerification(true); - } - final ConnectionFactory connectionFactory = connectionFactoryBuilder.build(); + final ConnectionFactory connectionFactory = createConnectionFactory(config, transcoder, + opQueueFactory, metricCollector); final List hosts = AddrUtil.getAddresses(config.getHosts()); @@ -422,6 +386,45 @@ public MemcachedClientIF get() } } + public static ConnectionFactory createConnectionFactory(final MemcachedCacheConfig config, final LZ4Transcoder transcoder, final OperationQueueFactory opQueueFactory, final MetricCollector metricCollector) throws KeyManagementException, KeyStoreException, NoSuchAlgorithmException { + MemcachedCustomConnectionFactoryBuilder connectionFactoryBuilder = (MemcachedCustomConnectionFactoryBuilder) new MemcachedCustomConnectionFactoryBuilder() + // 1000 repetitions gives us good distribution with murmur3_128 + // (approx < 5% difference in counts across nodes, with 5 cache nodes) + .setKetamaNodeRepetitions(1000) + .setHashAlg(MURMUR3_128) + .setProtocol(ConnectionFactoryBuilder.Protocol.valueOf(StringUtils.toUpperCase(config.getProtocol()))) + .setLocatorType(ConnectionFactoryBuilder.Locator.valueOf(StringUtils.toUpperCase(config.getLocator()))) + .setDaemon(true) + .setFailureMode(FailureMode.Cancel) + .setTranscoder(transcoder) + .setShouldOptimize(true) + .setOpQueueMaxBlockTime(config.getTimeout()) + .setOpTimeout(config.getTimeout()) + .setReadBufferSize(config.getReadBufferSize()) + .setOpQueueFactory(opQueueFactory) + .setMetricCollector(metricCollector) + .setEnableMetrics(MetricType.DEBUG); // Not as scary as it sounds + if (config.enableTls()) { + // Build SSLContext + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + // Create the client in TLS mode + connectionFactoryBuilder.setSSLContext(sslContext); + } + if ("dynamic".equals(config.getClientMode())) { + connectionFactoryBuilder.setClientMode(ClientMode.Dynamic); + connectionFactoryBuilder.setHostnameForTlsVerification(config.getHosts().split(",")[0]); + } else if ("static".equals(config.getClientMode())) { + connectionFactoryBuilder.setClientMode(ClientMode.Static); + } else { + throw new RuntimeException("Invalid value provided for `druid.cache.clientMode`. Value must be 'static' or 'dynamic'."); + } + connectionFactoryBuilder.setSkipTlsHostnameVerification(config.skipTlsHostnameVerification()); + return connectionFactoryBuilder.build(); + } + private final int timeout; private final int expiration; private final String memcachedPrefix; diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java index 5ca33e4bc7f3..ab6ce2883f25 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java @@ -56,7 +56,7 @@ public MemcachedCustomConnectionFactoryBuilder setKetamaNodeRepetitions(int repe @Override public ConnectionFactory build() { - return new DefaultConnectionFactory() + return new DefaultConnectionFactory(clientMode) { @Override public NodeLocator createLocator(List nodes) diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index 3f20706ea40c..b4e73fd13220 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -55,6 +55,9 @@ import org.junit.Test; import java.net.SocketAddress; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -218,34 +221,31 @@ public void emit(Event event) } @Test - public void testSslConnection() - { + public void testClientMode() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { final MemcachedCacheConfig config = new MemcachedCacheConfig() { @Override - public boolean enableTls() - { + public boolean enableTls() { return true; } + @Override + public String getClientMode() { + return "dynamic"; + } @Override public String getHosts() { return "localhost:9999"; } }; - // Static Mode - Assert.assertEquals(config.getClientMode(), "static"); - MemcachedCache client = new MemcachedCache( - Suppliers.ofInstance( - StupidResourceHolder.create(new MockMemcachedClient()) - ), - config, - NOOP_MONITOR - ); - Assert.assertNull(client.get(new Cache.NamedKey("a", HI))); - put(client, "a", HI, 1); - Assert.assertEquals(1, get(client, "a", HI)); + ConnectionFactory connectionFactory = MemcachedCache.createConnectionFactory(memcachedCacheConfig, null, null, null); + // Ensure client mode is set to the value passed in config. Default is static + Assert.assertEquals(connectionFactory.getClientMode(), ClientMode.Static); + // Dynamic mode + ConnectionFactory connectionFactoryDynamic = MemcachedCache.createConnectionFactory(config, null, null, null); + // Ensure client mode is set to the value passed in config. Default is static + Assert.assertEquals(connectionFactoryDynamic.getClientMode(), ClientMode.Dynamic); } @Test From 4f1507e9d0e79847568d5012fb946e62ad11987d Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 12 Sep 2023 08:43:18 +0530 Subject: [PATCH 13/20] fix checkstyle issues --- .../druid/client/cache/MemcachedCache.java | 9 +++++++-- .../client/cache/MemcachedCacheTest.java | 20 +++++++++++++++---- website/static/css/custom.css | 12 ----------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index ee148078936b..6f05ec3bb24a 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -63,7 +63,11 @@ import java.nio.charset.StandardCharsets; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; @@ -386,7 +390,8 @@ public MemcachedClientIF get() } } - public static ConnectionFactory createConnectionFactory(final MemcachedCacheConfig config, final LZ4Transcoder transcoder, final OperationQueueFactory opQueueFactory, final MetricCollector metricCollector) throws KeyManagementException, KeyStoreException, NoSuchAlgorithmException { + public static ConnectionFactory createConnectionFactory(final MemcachedCacheConfig config, final LZ4Transcoder transcoder, final OperationQueueFactory opQueueFactory, final MetricCollector metricCollector) throws KeyManagementException, KeyStoreException, NoSuchAlgorithmException + { MemcachedCustomConnectionFactoryBuilder connectionFactoryBuilder = (MemcachedCustomConnectionFactoryBuilder) new MemcachedCustomConnectionFactoryBuilder() // 1000 repetitions gives us good distribution with murmur3_128 // (approx < 5% difference in counts across nodes, with 5 cache nodes) diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index b4e73fd13220..e2dd87cd3a9d 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -29,7 +29,16 @@ import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.name.Names; -import net.spy.memcached.*; +import net.spy.memcached.BroadcastOpFactory; +import net.spy.memcached.CASResponse; +import net.spy.memcached.CASValue; +import net.spy.memcached.CachedData; +import net.spy.memcached.ClientMode; +import net.spy.memcached.ConnectionFactory; +import net.spy.memcached.ConnectionObserver; +import net.spy.memcached.MemcachedClientIF; +import net.spy.memcached.MemcachedNode; +import net.spy.memcached.NodeLocator; import net.spy.memcached.internal.BulkFuture; import net.spy.memcached.internal.BulkGetCompletionListener; import net.spy.memcached.internal.OperationFuture; @@ -221,16 +230,19 @@ public void emit(Event event) } @Test - public void testClientMode() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { + public void testClientMode() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException + { final MemcachedCacheConfig config = new MemcachedCacheConfig() { @Override - public boolean enableTls() { + public boolean enableTls() + { return true; } @Override - public String getClientMode() { + public String getClientMode() + { return "dynamic"; } @Override diff --git a/website/static/css/custom.css b/website/static/css/custom.css index c625c6826253..c73a53081425 100644 --- a/website/static/css/custom.css +++ b/website/static/css/custom.css @@ -99,15 +99,3 @@ article iframe { margin-right: auto; max-width: 100%; } -.getAPI { - color: #0073e6; - font-weight: bold; -} -.postAPI { - color: #00bf7d; - font-weight: bold; -} -.deleteAPI { - color: #f49200; - font-weight: bold; -} \ No newline at end of file From d37b2669ed78910c390b2e2f41e31e9f3b3fed94 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 12 Sep 2023 09:44:04 +0530 Subject: [PATCH 14/20] Fix cfbuilder.toString() --- ...mcachedCustomConnectionFactoryBuilder.java | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java index ab6ce2883f25..ad82ec9bfbad 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java @@ -19,17 +19,7 @@ package org.apache.druid.client.cache; -import net.spy.memcached.ArrayModNodeLocator; -import net.spy.memcached.ConnectionFactory; -import net.spy.memcached.ConnectionFactoryBuilder; -import net.spy.memcached.ConnectionObserver; -import net.spy.memcached.DefaultConnectionFactory; -import net.spy.memcached.FailureMode; -import net.spy.memcached.HashAlgorithm; -import net.spy.memcached.KetamaNodeLocator; -import net.spy.memcached.MemcachedNode; -import net.spy.memcached.NodeLocator; -import net.spy.memcached.OperationFactory; +import net.spy.memcached.*; import net.spy.memcached.auth.AuthDescriptor; import net.spy.memcached.metrics.MetricCollector; import net.spy.memcached.metrics.MetricType; @@ -37,6 +27,7 @@ import net.spy.memcached.transcoders.Transcoder; import net.spy.memcached.util.DefaultKetamaNodeLocatorConfiguration; +import javax.net.ssl.SSLContext; import java.util.Collection; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -213,6 +204,45 @@ public long getAuthWaitTime() { return authWaitTime; } + + @Override + public SSLContext getSSLContext() + { + return sslContext == null ? super.getSSLContext() : sslContext; + } + + @Override + public String getHostnameForTlsVerification() + { + return hostnameForTlsVerification == null ? super.getHostnameForTlsVerification() : hostnameForTlsVerification; + } + @Override + public ClientMode getClientMode() + { + return clientMode == null ? super.getClientMode() : clientMode; + } + + @Override + public boolean skipTlsHostnameVerification() + { + return skipTlsHostnameVerification; + } + + @Override + public String toString() + { + // MURMUR_128 cannot be cast to DefaultHashAlgorithm + return "Failure Mode: " + getFailureMode().name() + ", Hash Algorithm: " + + getHashAlg().toString() + " Max Reconnect Delay: " + + getMaxReconnectDelay() + ", Max Op Timeout: " + getOperationTimeout() + + ", Op Queue Length: " + getOpQueueLen() + ", Op Max Queue Block Time" + + getOpQueueMaxBlockTime() + ", Max Timeout Exception Threshold: " + + getTimeoutExceptionThreshold() + ", Read Buffer Size: " + + getReadBufSize() + ", Transcoder: " + getDefaultTranscoder() + + ", Operation Factory: " + getOperationFactory() + " isDaemon: " + + isDaemon() + ", Optimized: " + shouldOptimize() + ", Using Nagle: " + + useNagleAlgorithm() + ", KeepAlive: " + getKeepAlive() + ", SSLContext: " + getSSLContext() + ", ConnectionFactory: " + getName(); + } }; } } From c70573d9e8b051dd4cd7963be6712d422f737bbf Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 12 Sep 2023 21:28:32 +0530 Subject: [PATCH 15/20] check sslcontext is passed to connectionFactory --- .../java/org/apache/druid/client/cache/MemcachedCacheTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index e2dd87cd3a9d..805ab9a65873 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -254,10 +254,13 @@ public String getHosts() ConnectionFactory connectionFactory = MemcachedCache.createConnectionFactory(memcachedCacheConfig, null, null, null); // Ensure client mode is set to the value passed in config. Default is static Assert.assertEquals(connectionFactory.getClientMode(), ClientMode.Static); + Assert.assertNull(connectionFactory.getSSLContext()); // Dynamic mode ConnectionFactory connectionFactoryDynamic = MemcachedCache.createConnectionFactory(config, null, null, null); // Ensure client mode is set to the value passed in config. Default is static Assert.assertEquals(connectionFactoryDynamic.getClientMode(), ClientMode.Dynamic); + //enableTls is true so sslContext is not null + Assert.assertNotNull(connectionFactoryDynamic.getSSLContext()); } @Test From 11490d4307945ed086fd6d326ed890f670ca0871 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 12 Sep 2023 22:09:53 +0530 Subject: [PATCH 16/20] refactor unit tests --- .../client/cache/MemcachedCacheTest.java | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index 805ab9a65873..39bd5bbe28ce 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -62,6 +62,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.function.ThrowingRunnable; import java.net.SocketAddress; import java.security.KeyManagementException; @@ -230,7 +231,15 @@ public void emit(Event event) } @Test - public void testClientMode() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException + public void testDefaultClientMode() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException + { + ConnectionFactory connectionFactory = MemcachedCache.createConnectionFactory(memcachedCacheConfig, null, null, null); + // Ensure that clientMode is set to Static by default + Assert.assertEquals(connectionFactory.getClientMode(), ClientMode.Static); + Assert.assertNull(connectionFactory.getSSLContext()); + } + @Test + public void testConnectionFactory() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { final MemcachedCacheConfig config = new MemcachedCacheConfig() { @@ -251,18 +260,29 @@ public String getHosts() return "localhost:9999"; } }; - ConnectionFactory connectionFactory = MemcachedCache.createConnectionFactory(memcachedCacheConfig, null, null, null); - // Ensure client mode is set to the value passed in config. Default is static - Assert.assertEquals(connectionFactory.getClientMode(), ClientMode.Static); - Assert.assertNull(connectionFactory.getSSLContext()); // Dynamic mode ConnectionFactory connectionFactoryDynamic = MemcachedCache.createConnectionFactory(config, null, null, null); - // Ensure client mode is set to the value passed in config. Default is static + // Ensure client mode is set to the value passed in config. Assert.assertEquals(connectionFactoryDynamic.getClientMode(), ClientMode.Dynamic); //enableTls is true so sslContext is not null Assert.assertNotNull(connectionFactoryDynamic.getSSLContext()); } + @Test + public void testInvalidClientMode() + { + final MemcachedCacheConfig config = new MemcachedCacheConfig() + { + + @Override + public String getClientMode() + { + return "invalid-name"; + } + }; + RuntimeException exception = Assert.assertThrows(RuntimeException.class, () -> {MemcachedCache.createConnectionFactory(config, null, null, null);}); + Assert.assertEquals(exception.getMessage(), "Invalid value provided for `druid.cache.clientMode`. Value must be 'static' or 'dynamic'."); + } @Test public void testSanity() { From cc1cdb5ec4cd83bdddf6cc551ef6c7c4e15d8a5f Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 12 Sep 2023 22:14:36 +0530 Subject: [PATCH 17/20] revert custom.css changes --- website/static/css/custom.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/static/css/custom.css b/website/static/css/custom.css index c73a53081425..a70cd5797a0d 100644 --- a/website/static/css/custom.css +++ b/website/static/css/custom.css @@ -99,3 +99,15 @@ article iframe { margin-right: auto; max-width: 100%; } +.getAPI { + color: #0073e6; + font-weight: bold; +} +.postAPI { + color: #00bf7d; + font-weight: bold; +} +.deleteAPI { + color: #f49200; + font-weight: bold; +} \ No newline at end of file From 3e88827b7623cf9e37123f6f7aca8261383f6122 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 20 Sep 2023 20:34:44 +0530 Subject: [PATCH 18/20] checkstyle fixes --- .../apache/druid/client/cache/MemcachedCache.java | 10 +++++----- .../MemcachedCustomConnectionFactoryBuilder.java | 13 ++++++++++++- .../druid/client/cache/MemcachedCacheTest.java | 5 +++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java index 23d9f88f9b10..d67e01b110b3 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCache.java @@ -29,10 +29,6 @@ import com.google.common.collect.Maps; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; -import java.security.KeyManagementException; -import java.security.KeyStore; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; import net.spy.memcached.AddrUtil; import net.spy.memcached.ClientMode; import net.spy.memcached.ConnectionFactory; @@ -57,17 +53,21 @@ import org.apache.druid.java.util.metrics.AbstractMonitor; import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java index ad82ec9bfbad..ea9ec03dff7b 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java @@ -19,7 +19,18 @@ package org.apache.druid.client.cache; -import net.spy.memcached.*; +import net.spy.memcached.ArrayModNodeLocator; +import net.spy.memcached.ClientMode; +import net.spy.memcached.ConnectionFactory; +import net.spy.memcached.ConnectionFactoryBuilder; +import net.spy.memcached.ConnectionObserver; +import net.spy.memcached.DefaultConnectionFactory; +import net.spy.memcached.FailureMode; +import net.spy.memcached.HashAlgorithm; +import net.spy.memcached.KetamaNodeLocator; +import net.spy.memcached.MemcachedNode; +import net.spy.memcached.NodeLocator; +import net.spy.memcached.OperationFactory; import net.spy.memcached.auth.AuthDescriptor; import net.spy.memcached.metrics.MetricCollector; import net.spy.memcached.metrics.MetricType; diff --git a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java index 39bd5bbe28ce..831150fd6771 100644 --- a/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java +++ b/server/src/test/java/org/apache/druid/client/cache/MemcachedCacheTest.java @@ -62,7 +62,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.junit.function.ThrowingRunnable; import java.net.SocketAddress; import java.security.KeyManagementException; @@ -280,7 +279,9 @@ public String getClientMode() return "invalid-name"; } }; - RuntimeException exception = Assert.assertThrows(RuntimeException.class, () -> {MemcachedCache.createConnectionFactory(config, null, null, null);}); + RuntimeException exception = Assert.assertThrows(RuntimeException.class, () -> { + MemcachedCache.createConnectionFactory(config, null, null, null); + }); Assert.assertEquals(exception.getMessage(), "Invalid value provided for `druid.cache.clientMode`. Value must be 'static' or 'dynamic'."); } @Test From f8dbaffc9d2cfded1a0409acaca3f253f090211d Mon Sep 17 00:00:00 2001 From: pagrawal Date: Thu, 21 Sep 2023 10:46:16 +0530 Subject: [PATCH 19/20] minor nits in toString() --- .../client/cache/MemcachedCustomConnectionFactoryBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java index ea9ec03dff7b..d5b182a9adfe 100644 --- a/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java +++ b/server/src/main/java/org/apache/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java @@ -244,7 +244,7 @@ public String toString() { // MURMUR_128 cannot be cast to DefaultHashAlgorithm return "Failure Mode: " + getFailureMode().name() + ", Hash Algorithm: " - + getHashAlg().toString() + " Max Reconnect Delay: " + + getHashAlg() + " Max Reconnect Delay: " + getMaxReconnectDelay() + ", Max Op Timeout: " + getOperationTimeout() + ", Op Queue Length: " + getOpQueueLen() + ", Op Max Queue Block Time" + getOpQueueMaxBlockTime() + ", Max Timeout Exception Threshold: " @@ -252,7 +252,7 @@ public String toString() + getReadBufSize() + ", Transcoder: " + getDefaultTranscoder() + ", Operation Factory: " + getOperationFactory() + " isDaemon: " + isDaemon() + ", Optimized: " + shouldOptimize() + ", Using Nagle: " - + useNagleAlgorithm() + ", KeepAlive: " + getKeepAlive() + ", SSLContext: " + getSSLContext() + ", ConnectionFactory: " + getName(); + + useNagleAlgorithm() + ", KeepAlive: " + getKeepAlive() + ", SSLContext: " + getSSLContext().getProtocol() + ", ConnectionFactory: " + getName(); } }; } From 1b5a8ce288ec56d2e6dffa972d1a0a03e8e51eaf Mon Sep 17 00:00:00 2001 From: Parth Agrawal <98726675+pagrawal10@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:54:33 +0530 Subject: [PATCH 20/20] Update custom.css --- website/static/css/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/static/css/custom.css b/website/static/css/custom.css index 702e1ccfdf4c..c73a53081425 100644 --- a/website/static/css/custom.css +++ b/website/static/css/custom.css @@ -98,4 +98,4 @@ article iframe { margin-left: auto; margin-right: auto; max-width: 100%; -} \ No newline at end of file +}