From 4db8e0b43df06d48e72820031f8b5ef55bf36975 Mon Sep 17 00:00:00 2001 From: Raghuram Onti Srinivasan Date: Mon, 3 Jun 2019 13:51:33 -0700 Subject: [PATCH] Adding Dyno lock to enable using redis for distributed locking using Redlock --- build.gradle | 16 + .../ArchaiusConnectionPoolConfiguration.java | 7 + .../dyno/contrib/EurekaHostsSupplier.java | 3 +- .../contrib/consul/ConsulHostsSupplier.java | 25 +- .../connectionpool/ConnectionFactory.java | 6 +- .../connectionpool/ConnectionObservor.java | 40 -- .../ConnectionPoolConfiguration.java | 15 + .../com/netflix/dyno/connectionpool/Host.java | 81 ++-- .../dyno/connectionpool/HostBuilder.java | 76 ++++ .../dyno/connectionpool/HostGroup.java | 52 --- .../impl/ConnectionPoolConfigurationImpl.java | 39 +- .../impl/ConnectionPoolImpl.java | 54 ++- .../impl/CountingConnectionPoolMonitor.java | 22 +- .../impl/HostConnectionPoolImpl.java | 26 +- .../connectionpool/impl/HostsUpdater.java | 28 +- .../impl/SimpleAsyncConnectionPoolImpl.java | 19 +- .../impl/lb/AbstractTokenMapSupplier.java | 3 +- .../impl/lb/HostSelectionWithFallback.java | 73 +-- .../connectionpool/impl/lb/HostUtils.java | 80 ++++ .../impl/ConnectionPoolImplTest.java | 77 ++-- .../CountingConnectionPoolMonitorTest.java | 5 +- .../impl/HostConnectionPoolImplTest.java | 41 +- .../impl/HostStatusTrackerTest.java | 3 +- .../impl/OperationResultImplTest.java | 3 +- .../SimpleAsyncConnectionPoolImplTest.java | 40 +- .../hash/BinarySearchTokenMapperTest.java | 25 +- .../ConnectionPoolHealthTrackerTest.java | 5 +- .../impl/lb/AbstractTokenMapSupplierTest.java | 17 +- .../lb/HostSelectionWithFallbackTest.java | 109 ++--- .../connectionpool/impl/lb/HostTokenTest.java | 21 +- .../impl/lb/RoundRobinSelectionTest.java | 10 +- .../lb/TokenAwareSelectionBinaryTest.java | 18 +- .../lb/TokenAwareSelectionHastagTest.java | 26 +- .../impl/lb/TokenAwareSelectionTest.java | 18 +- .../impl/lb/TokenMapSupplierTest.java | 49 +- .../redis/CustomTokenSupplierExample.java | 4 +- .../redis/DynoDistributedCounterDemo.java | 2 +- .../dyno/demo/redis/DynoJedisDemo.java | 75 ++- dyno-demo/src/main/resources/demo.properties | 8 +- .../netflix/dyno/jedis/DynoJedisClient.java | 247 +--------- .../netflix/dyno/jedis/DynoJedisUtils.java | 164 +++++++ .../dyno/jedis/JedisConnectionFactory.java | 33 +- .../jedis/operation/BaseKeyOperation.java | 39 ++ .../jedis/operation/MultiKeyOperation.java | 61 +++ ...JedisConnectionFactoryIntegrationTest.java | 3 +- .../RedisAuthenticationIntegrationTest.java | 37 +- .../UnitTestTokenMapAndHostSupplierImpl.java | 3 +- .../dyno/recipes/lock/DynoLockClient.java | 428 ++++++++++++++++++ .../dyno/recipes/lock/LockResource.java | 30 ++ .../lock/VotingHostsFromTokenRange.java | 92 ++++ .../recipes/lock/VotingHostsSelector.java | 18 + .../recipes/lock/command/CheckAndRunHost.java | 53 +++ .../recipes/lock/command/CommandHost.java | 35 ++ .../dyno/recipes/lock/command/ExtendHost.java | 61 +++ .../dyno/recipes/lock/command/LockHost.java | 51 +++ .../dyno/recipes/lock/DynoLockClientTest.java | 156 +++++++ .../dyno/recipes/lock/LocalRedisLockTest.java | 61 +++ .../lock/VotingHostsFromTokenRangeTest.java | 78 ++++ .../redisson/RedissonConnectionFactory.java | 20 +- gradlew | 2 +- 60 files changed, 2097 insertions(+), 796 deletions(-) delete mode 100644 dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionObservor.java create mode 100644 dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostBuilder.java delete mode 100644 dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostGroup.java create mode 100644 dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostUtils.java create mode 100644 dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisUtils.java create mode 100644 dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/BaseKeyOperation.java create mode 100644 dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/MultiKeyOperation.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/DynoLockClient.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/LockResource.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRange.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsSelector.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CheckAndRunHost.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CommandHost.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/ExtendHost.java create mode 100644 dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/LockHost.java create mode 100644 dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/DynoLockClientTest.java create mode 100644 dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/LocalRedisLockTest.java create mode 100644 dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRangeTest.java diff --git a/build.gradle b/build.gradle index 40fa613c..b3ab2fd4 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ subprojects { apply plugin: 'java' apply plugin: 'idea' apply plugin: 'eclipse' + apply plugin: 'jacoco' sourceCompatibility = 1.8 repositories { @@ -38,6 +39,16 @@ subprojects { options.addStringOption('Xdoclint:none', '-quiet') } } + + jacocoTestReport { + reports { + xml.enabled false + csv.enabled false + html.enabled true + } + } + + test.finalizedBy(project.tasks.jacocoTestReport) } project(':dyno-core') { @@ -133,5 +144,10 @@ project(':dyno-recipes') { dependencies { compile project(':dyno-core') compile project(':dyno-jedis') + testCompile 'com.netflix.spinnaker.embedded-redis:embedded-redis:0.8.0' + } + + test { + testLogging.showStandardStreams = true } } diff --git a/dyno-contrib/src/main/java/com/netflix/dyno/contrib/ArchaiusConnectionPoolConfiguration.java b/dyno-contrib/src/main/java/com/netflix/dyno/contrib/ArchaiusConnectionPoolConfiguration.java index 8f2bf74f..8e921435 100644 --- a/dyno-contrib/src/main/java/com/netflix/dyno/contrib/ArchaiusConnectionPoolConfiguration.java +++ b/dyno-contrib/src/main/java/com/netflix/dyno/contrib/ArchaiusConnectionPoolConfiguration.java @@ -52,6 +52,7 @@ public class ArchaiusConnectionPoolConfiguration extends ConnectionPoolConfigura private final ErrorRateMonitorConfig errorRateConfig; private final RetryPolicyFactory retryPolicyFactory; private final DynamicBooleanProperty failOnStartupIfNoHosts; + private final DynamicIntProperty lockVotingSize; private DynamicBooleanProperty isDualWriteEnabled; private DynamicStringProperty dualWriteClusterName; @@ -72,6 +73,7 @@ public ArchaiusConnectionPoolConfiguration(String name) { configPublisherConfig = DynamicPropertyFactory.getInstance().getStringProperty(propertyPrefix + ".config.publisher.address", super.getConfigurationPublisherConfig()); failOnStartupIfNoHosts = DynamicPropertyFactory.getInstance().getBooleanProperty(propertyPrefix + ".config.startup.failIfNoHosts", super.getFailOnStartupIfNoHosts()); compressionThreshold = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".config.compressionThreshold", super.getValueCompressionThreshold()); + lockVotingSize = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".config.lock.votingSize", super.getLockVotingSize()); loadBalanceStrategy = parseLBStrategy(propertyPrefix); errorRateConfig = parseErrorRateMonitorConfig(propertyPrefix); @@ -171,6 +173,11 @@ public int getDualWritePercentage() { return dualWritePercentage.get(); } + @Override + public int getLockVotingSize() { + return lockVotingSize.get(); + } + public void setIsDualWriteEnabled(DynamicBooleanProperty booleanProperty) { this.isDualWriteEnabled = booleanProperty; } diff --git a/dyno-contrib/src/main/java/com/netflix/dyno/contrib/EurekaHostsSupplier.java b/dyno-contrib/src/main/java/com/netflix/dyno/contrib/EurekaHostsSupplier.java index 95c1796d..afe925e1 100644 --- a/dyno-contrib/src/main/java/com/netflix/dyno/contrib/EurekaHostsSupplier.java +++ b/dyno-contrib/src/main/java/com/netflix/dyno/contrib/EurekaHostsSupplier.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import com.netflix.dyno.connectionpool.HostBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,7 +115,7 @@ public Host apply(InstanceInfo info) { if (rack == null) { Logger.error("Rack wasn't found for host:" + info.getHostName() + " there may be issues matching it up to the token map"); } - Host host = new Host(info.getHostName(), info.getIPAddr(), rack, status); + Host host = new HostBuilder().setHostname(info.getHostName()).setIpAddress(info.getIPAddr()).setRack(rack).setStatus(status).createHost(); return host; } })); diff --git a/dyno-contrib/src/main/java/com/netflix/dyno/contrib/consul/ConsulHostsSupplier.java b/dyno-contrib/src/main/java/com/netflix/dyno/contrib/consul/ConsulHostsSupplier.java index b643c439..2f25e3e0 100644 --- a/dyno-contrib/src/main/java/com/netflix/dyno/contrib/consul/ConsulHostsSupplier.java +++ b/dyno-contrib/src/main/java/com/netflix/dyno/contrib/consul/ConsulHostsSupplier.java @@ -15,14 +15,6 @@ */ package com.netflix.dyno.contrib.consul; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.QueryParams; import com.ecwid.consul.v1.Response; @@ -34,7 +26,15 @@ import com.google.common.collect.Lists; import com.netflix.discovery.DiscoveryManager; import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostSupplier; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; /** * Simple class that implements {@link Supplier}<{@link List}<{@link Host}>>. It provides a List<{@link Host}> @@ -129,8 +129,13 @@ public Host apply(HealthService info) { Logger.error("Rack wasn't found for host:" + info.getNode() + " there may be issues matching it up to the token map"); } - Host host = new Host(hostName, hostName, info.getService().getPort(), rack, - String.valueOf(metaData.get("datacenter")), status); + Host host = new HostBuilder().setHostname(hostName) + .setIpAddress(hostName) + .setPort(info.getService().getPort()) + .setRack(rack) + .setDatacenter(String.valueOf(metaData.get("datacenter"))) + .setStatus(status) + .createHost(); return host; } })); diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionFactory.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionFactory.java index b674715c..666c2cc9 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionFactory.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionFactory.java @@ -31,10 +31,12 @@ public interface ConnectionFactory { * Create a connection for this {@link HostConnectionPool} * * @param pool - * @param observor * @return * @throws DynoConnectException * @throws ThrottledException */ - public Connection createConnection(HostConnectionPool pool, ConnectionObservor observor) throws DynoConnectException, ThrottledException; + Connection createConnection(HostConnectionPool pool) throws DynoConnectException; + + Connection createConnectionWithDataStore(HostConnectionPool pool) + throws DynoConnectException; } diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionObservor.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionObservor.java deleted file mode 100644 index bc1a0657..00000000 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionObservor.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright 2011 Netflix - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -package com.netflix.dyno.connectionpool; - -/** - * Interface for observers to connections becoming active and inactive. - * Observers can then track stats about this and even implement connection recycling - * in order to keep the connection pool primed with active connections - * - * @author poberai - */ -public interface ConnectionObservor { - - /** - * Connection has been established for this host - * - * @param host - */ - public void connectionEstablished(Host host); - - /** - * Connection to this host was lost - * - * @param host - */ - public void connectionLost(Host host); -} diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionPoolConfiguration.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionPoolConfiguration.java index 860b493b..6395a17d 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionPoolConfiguration.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/ConnectionPoolConfiguration.java @@ -39,6 +39,20 @@ enum CompressionStrategy { THRESHOLD } + /** + * Should connections in this pool connect to the datastore directly? + * @return + */ + boolean isConnectToDatastore(); + + boolean isFallbackEnabled(); + + /** + * Returns the voting size for dyno lock + * @return + */ + int getLockVotingSize(); + /** * Returns the unique name assigned to this connection pool. */ @@ -224,4 +238,5 @@ enum CompressionStrategy { String getHashtag(); + ConnectionPoolConfiguration setLocalZoneAffinity(boolean condition); } diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/Host.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/Host.java index 22d43178..ac2a53b1 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/Host.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/Host.java @@ -15,12 +15,11 @@ ******************************************************************************/ package com.netflix.dyno.connectionpool; +import org.apache.commons.lang3.StringUtils; + import java.net.InetSocketAddress; import java.util.Objects; -import com.netflix.dyno.connectionpool.impl.utils.ConfigUtils; -import org.apache.commons.lang3.StringUtils; - /** * Class encapsulating information about a host. *

@@ -35,12 +34,15 @@ public class Host implements Comparable { public static final int DEFAULT_PORT = 8102; - public static final Host NO_HOST = new Host("UNKNOWN", "UNKNOWN", 0, "UNKNOWN"); + public static final int DEFAULT_DATASTORE_PORT = 22122; + public static final Host NO_HOST = new HostBuilder().setHostname("UNKNOWN").setIpAddress("UNKNOWN").setPort(0) + .setRack("UNKNOWN").createHost(); private final String hostname; private final String ipAddress; private final int port; private final int securePort; + private final int datastorePort; private final InetSocketAddress socketAddress; private final String rack; private final String datacenter; @@ -52,56 +54,12 @@ public enum Status { Up, Down; } - public Host(String hostname, int port, String rack) { - this(hostname, null, port, port, rack, ConfigUtils.getDataCenterFromRack(rack), Status.Down, null); - } - - public Host(String hostname, String rack, Status status) { - this(hostname, null, DEFAULT_PORT, DEFAULT_PORT, rack, ConfigUtils.getDataCenterFromRack(rack), status, null); - } - - public Host(String hostname, int port, String rack, Status status) { - this(hostname, null, port, port, rack, ConfigUtils.getDataCenterFromRack(rack), status, null); - } - - public Host(String hostname, int port, String rack, Status status, String hashtag) { - this(hostname, null, port, port, rack, ConfigUtils.getDataCenterFromRack(rack), status, hashtag); - } - - public Host(String hostname, int port, String rack, Status status, String hashtag, String password) { - this(hostname, null, port, port, rack, ConfigUtils.getDataCenterFromRack(rack), status, hashtag, password); - } - - public Host(String hostname, String ipAddress, int port, String rack) { - this(hostname, ipAddress, port, port, rack, ConfigUtils.getDataCenterFromRack(rack), Status.Down, null); - } - - public Host(String hostname, String ipAddress, String rack, Status status) { - this(hostname, ipAddress, DEFAULT_PORT, DEFAULT_PORT, rack, ConfigUtils.getDataCenterFromRack(rack), status, null); - } - - public Host(String hostname, String ipAddress, String rack, Status status, String hashtag) { - this(hostname, ipAddress, DEFAULT_PORT, DEFAULT_PORT, rack, ConfigUtils.getDataCenterFromRack(rack), status, hashtag); - } - - public Host(String hostname, String ipAddress, int port, String rack, String datacenter, Status status) { - this(hostname, ipAddress, port, port, rack, datacenter, status, null); - } - - public Host(String name, String ipAddress, int port, String rack, String datacenter, Status status, - String hashtag) { - this(name, ipAddress, port, port, rack, datacenter, status, hashtag); - } - - public Host(String name, String ipAddress, int port, int securePort, String rack, String datacenter, Status status, String hashtag) { - this(name, ipAddress, port, port, rack, datacenter, status, hashtag, null); - } - - public Host(String name, String ipAddress, int port, int securePort, String rack, String datacenter, Status status, String hashtag, String password) { - this.hostname = name; + public Host(String hostname, String ipAddress, int port, int securePort, int datastorePort, String rack, String datacenter, Status status, String hashtag, String password) { + this.hostname = hostname; this.ipAddress = ipAddress; this.port = port; this.securePort = securePort; + this.datastorePort = datastorePort; this.rack = rack; this.status = status; this.datacenter = datacenter; @@ -110,7 +68,7 @@ public Host(String name, String ipAddress, int port, int securePort, String rack // Used for the unit tests to prevent host name resolution if (port != -1) { - this.socketAddress = new InetSocketAddress(name, port); + this.socketAddress = new InetSocketAddress(hostname, port); } else { this.socketAddress = null; } @@ -139,6 +97,10 @@ public int getSecurePort() { return securePort; } + public int getDatastorePort() { + return datastorePort; + } + public String getDatacenter() { return datacenter; } @@ -168,6 +130,10 @@ public String getPassword() { return password; } + public Status getStatus() { + return status; + } + public InetSocketAddress getSocketAddress() { return socketAddress; } @@ -223,4 +189,15 @@ public String toString() { + rack + ", datacenter: " + datacenter + ", status: " + status.name() + ", hashtag=" + hashtag + ", password=" + (Objects.nonNull(password) ? "masked" : "null") + "]"; } + + public static Host clone(Host host) { + return new HostBuilder().setHostname(host.getHostName()) + .setIpAddress(host.getIpAddress()).setPort(host.getPort()) + .setSecurePort(host.getSecurePort()) + .setRack(host.getRack()) + .setDatastorePort(host.getDatastorePort()) + .setDatacenter(host.getDatacenter()).setStatus(host.getStatus()) + .setHashtag(host.getHashtag()) + .setPassword(host.getPassword()).createHost(); + } } diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostBuilder.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostBuilder.java new file mode 100644 index 00000000..953e58c6 --- /dev/null +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostBuilder.java @@ -0,0 +1,76 @@ +package com.netflix.dyno.connectionpool; + +import com.netflix.dyno.connectionpool.impl.utils.ConfigUtils; + +import static com.netflix.dyno.connectionpool.Host.DEFAULT_DATASTORE_PORT; +import static com.netflix.dyno.connectionpool.Host.DEFAULT_PORT; + +public class HostBuilder { + private String hostname; + private int port = DEFAULT_PORT; + private String rack; + private String ipAddress = null; + private int securePort = DEFAULT_PORT; + private int datastorePort = DEFAULT_DATASTORE_PORT; + private String datacenter = null; + private Host.Status status = Host.Status.Down; + private String hashtag = null; + private String password = null; + + public HostBuilder setPort(int port) { + this.port = port; + return this; + } + + public HostBuilder setRack(String rack) { + this.rack = rack; + return this; + } + + public HostBuilder setHostname(String hostname) { + this.hostname = hostname; + return this; + } + + public HostBuilder setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + return this; + } + + public HostBuilder setSecurePort(int securePort) { + this.securePort = securePort; + return this; + } + + public HostBuilder setDatacenter(String datacenter) { + this.datacenter = datacenter; + return this; + } + + public HostBuilder setStatus(Host.Status status) { + this.status = status; + return this; + } + + public HostBuilder setHashtag(String hashtag) { + this.hashtag = hashtag; + return this; + } + + public HostBuilder setPassword(String password) { + this.password = password; + return this; + } + + public HostBuilder setDatastorePort(int datastorePort) { + this.datastorePort = datastorePort; + return this; + } + + public Host createHost() { + if (datacenter == null) { + datacenter = ConfigUtils.getDataCenterFromRack(rack); + } + return new Host(hostname, ipAddress, port, securePort, datastorePort, rack, datacenter, status, hashtag, password); + } +} diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostGroup.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostGroup.java deleted file mode 100644 index dedbd3a8..00000000 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/HostGroup.java +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * Copyright 2011 Netflix - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -package com.netflix.dyno.connectionpool; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.netflix.dyno.connectionpool.exception.DynoConnectException; - -/** - * Class representing a group of hosts. This is useful for underlying connection pool implementations - * where a single multiplexed connection can be used to talk to a group of hosts. - * e.g spy memcached uses this approach where there is a single selector for a group of hosts. - * - * @author poberai - */ -public class HostGroup extends Host { - - private final List hostList = new ArrayList(); - - public HostGroup(String hostname, String ipAddress, int port, String rack) { - super(hostname, ipAddress, port, rack); - } - - public void add(Collection hosts) { - for (Host host : hosts) { - if (!host.isUp()) { - throw new DynoConnectException("Cannot add host that is DOWN"); - } - hostList.add(host); - } - } - - public List getHostList() { - return hostList; - } - -} diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolConfigurationImpl.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolConfigurationImpl.java index 7f81962e..474bdc1c 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolConfigurationImpl.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolConfigurationImpl.java @@ -15,9 +15,6 @@ ******************************************************************************/ package com.netflix.dyno.connectionpool.impl; -import java.util.ArrayList; -import java.util.List; - import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration; import com.netflix.dyno.connectionpool.ErrorRateMonitorConfig; import com.netflix.dyno.connectionpool.HashPartitioner; @@ -29,6 +26,9 @@ import com.netflix.dyno.connectionpool.impl.health.SimpleErrorMonitorImpl.SimpleErrorMonitorFactory; import com.netflix.dyno.connectionpool.impl.utils.ConfigUtils; +import java.util.ArrayList; +import java.util.List; + public class ConnectionPoolConfigurationImpl implements ConnectionPoolConfiguration { // DEFAULTS @@ -51,6 +51,9 @@ public class ConnectionPoolConfigurationImpl implements ConnectionPoolConfigurat private static final int DEFAULT_DUAL_WRITE_PERCENTAGE = 0; private static final int DEFAULT_HEALTH_TRACKER_DELAY_MILLIS = 10 * 1000; private static final int DEFAULT_POOL_RECONNECT_WAIT_MILLIS = 5 * 1000; + private static final boolean DEFAULT_FALLBACK_POLICY = true; + private static final boolean DEFAULT_CONNECT_TO_DATASTORE = false; + private static final int DEFAULT_LOCK_VOTING_SIZE = -1; private HostSupplier hostSupplier; private TokenMapSupplier tokenSupplier; @@ -81,6 +84,9 @@ public class ConnectionPoolConfigurationImpl implements ConnectionPoolConfigurat private int dualWritePercentage = DEFAULT_DUAL_WRITE_PERCENTAGE; private int healthTrackerDelayMillis = DEFAULT_HEALTH_TRACKER_DELAY_MILLIS; private int poolReconnectWaitMillis = DEFAULT_POOL_RECONNECT_WAIT_MILLIS; + private int lockVotingSize = DEFAULT_LOCK_VOTING_SIZE; + private boolean fallbackEnabled = DEFAULT_FALLBACK_POLICY; + private boolean connectToDatastore = DEFAULT_CONNECT_TO_DATASTORE; private RetryPolicyFactory retryFactory = new RetryPolicyFactory() { @@ -132,6 +138,22 @@ public ConnectionPoolConfigurationImpl(ConnectionPoolConfigurationImpl config) { this.hashtag = config.getHashtag(); this.healthTrackerDelayMillis = config.getHealthTrackerDelayMillis(); this.poolReconnectWaitMillis = config.getPoolReconnectWaitMillis(); + this.lockVotingSize = config.getLockVotingSize(); + } + + @Override + public boolean isConnectToDatastore() { + return connectToDatastore; + } + + @Override + public boolean isFallbackEnabled() { + return fallbackEnabled; + } + + @Override + public int getLockVotingSize() { + return lockVotingSize; } @Override @@ -139,7 +161,6 @@ public String getName() { return name; } - @Override public int getMaxConnsPerHost() { return maxConnsPerHost; @@ -288,10 +309,20 @@ public String toString() { ", hashtag=" + hashtag + ", healthTrackerDelayMillis=" + healthTrackerDelayMillis + ", poolReconnectWaitMillis=" + poolReconnectWaitMillis + + ", lockVotingSize =" + lockVotingSize + '}'; } // ALL SETTERS + public void setConnectToDatastore(boolean connectToDatastore) { + this.connectToDatastore = connectToDatastore; + } + + public ConnectionPoolConfigurationImpl setFallbackEnabled(boolean fallbackEnabled) { + this.fallbackEnabled = fallbackEnabled; + return this; + } + public ConnectionPoolConfigurationImpl setMaxConnsPerHost(int maxConnsPerHost) { this.maxConnsPerHost = maxConnsPerHost; return this; diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImpl.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImpl.java index b804bc87..f614b569 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImpl.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImpl.java @@ -15,30 +15,43 @@ ******************************************************************************/ package com.netflix.dyno.connectionpool.impl; -import java.lang.management.ManagementFactory; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; - import com.netflix.dyno.connectionpool.*; -import com.netflix.dyno.connectionpool.exception.FatalConnectionException; -import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.netflix.dyno.connectionpool.exception.DynoException; +import com.netflix.dyno.connectionpool.exception.FatalConnectionException; import com.netflix.dyno.connectionpool.exception.NoAvailableHostsException; +import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; import com.netflix.dyno.connectionpool.impl.HostConnectionPoolFactory.Type; import com.netflix.dyno.connectionpool.impl.health.ConnectionPoolHealthTracker; import com.netflix.dyno.connectionpool.impl.lb.HostSelectionWithFallback; import com.netflix.dyno.connectionpool.impl.utils.CollectionUtils; import com.netflix.dyno.connectionpool.impl.utils.CollectionUtils.Predicate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import javax.management.*; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * Main implementation class for {@link ConnectionPool} The pool deals with a @@ -292,8 +305,7 @@ public OperationResult executeWithFailover(Operation op) throws Dy connection = selectionStrategy.getConnectionUsingRetryPolicy(op, cpConfiguration.getMaxTimeoutWhenExhausted(), TimeUnit.MILLISECONDS, retry); - connection.getContext().setMetadata("host", connection.getHost().getHostAddress()); - connection.getContext().setMetadata("port", connection.getHost().getPort()); + updateConnectionContext(connection.getContext(), connection.getHost()); OperationResult result = connection.execute(op); @@ -381,7 +393,7 @@ public Collection> executeWithRing(TokenRackMapper tokenR do { try { - connection.getContext().setMetadata("host", connection.getHost().getHostAddress()); + updateConnectionContext(connection.getContext(), connection.getHost()); OperationResult result = connection.execute(op); // Add context to the result from the successful @@ -442,6 +454,12 @@ public Collection> executeWithRing(TokenRackMapper tokenR } } + private void updateConnectionContext(ConnectionContext context, Host host) { + context.setMetadata("host", host.getHostAddress()); + context.setMetadata("port", host.getPort()); + context.setMetadata("datastorePort", host.getDatastorePort()); + } + /** * Use with EXTREME CAUTION. Connection that is borrowed must be returned, * else we will have connection pool exhaustion diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitor.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitor.java index 94db8f39..4036c520 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitor.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitor.java @@ -15,19 +15,23 @@ ******************************************************************************/ package com.netflix.dyno.connectionpool.impl; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - import com.netflix.dyno.connectionpool.ConnectionPoolMonitor; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.HostConnectionStats; -import com.netflix.dyno.connectionpool.HostGroup; -import com.netflix.dyno.connectionpool.exception.*; +import com.netflix.dyno.connectionpool.exception.BadRequestException; +import com.netflix.dyno.connectionpool.exception.FatalConnectionException; +import com.netflix.dyno.connectionpool.exception.NoAvailableHostsException; +import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; +import com.netflix.dyno.connectionpool.exception.PoolTimeoutException; +import com.netflix.dyno.connectionpool.exception.TimeoutException; import com.netflix.dyno.connectionpool.impl.utils.EstimatedHistogram; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + /** * Impl of {@link ConnectionPoolMonitor} using thread safe AtomicLongs * @@ -155,7 +159,7 @@ public long getConnectionCreateFailedCount() { public void incConnectionBorrowed(Host host, long delay) { this.connectionBorrowCount.incrementAndGet(); this.borrowedConnHistogram.add(delay); - if (host == null || (host instanceof HostGroup)) { + if (host == null) { return; } getOrCreateHostStats(host).borrowed.incrementAndGet(); @@ -190,7 +194,7 @@ public void resetConnectionBorrowedLatStats() { @Override public void incConnectionReturned(Host host) { this.connectionReturnCount.incrementAndGet(); - if (host == null || (host instanceof HostGroup)) { + if (host == null) { return; } getOrCreateHostStats(host).returned.incrementAndGet(); diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImpl.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImpl.java index 29ab5209..3eeb62a6 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImpl.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImpl.java @@ -15,18 +15,6 @@ ******************************************************************************/ package com.netflix.dyno.connectionpool.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.netflix.dyno.connectionpool.Connection; import com.netflix.dyno.connectionpool.ConnectionFactory; import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration; @@ -36,8 +24,19 @@ import com.netflix.dyno.connectionpool.RetryPolicy; import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; +import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; import com.netflix.dyno.connectionpool.exception.PoolOfflineException; import com.netflix.dyno.connectionpool.exception.PoolTimeoutException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * Main impl for {@link HostConnectionPool} @@ -302,7 +301,8 @@ private ConnectionPoolActive(HostConnectionPoolImpl cp) { public Connection createConnection() { try { - Connection connection = connFactory.createConnection((HostConnectionPool) pool, null); + Connection connection = cpConfig.isConnectToDatastore() ? connFactory.createConnectionWithDataStore(pool) : + connFactory.createConnection(pool); connection.open(); availableConnections.add(connection); diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostsUpdater.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostsUpdater.java index 2cb0bfe4..649429dd 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostsUpdater.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/HostsUpdater.java @@ -15,10 +15,6 @@ */ package com.netflix.dyno.connectionpool.impl; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.TokenMapSupplier; @@ -28,6 +24,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + public class HostsUpdater { private static final Logger Logger = LoggerFactory.getLogger(ConnectionPoolImpl.class); @@ -110,18 +116,12 @@ public HostStatusTracker refreshHosts() { throw new DynoException("Could not find " + hostFromHostSupplier.getHostName() + " in token map supplier."); } - hostsUpFromHostSupplier.add(new Host(hostFromHostSupplier.getHostName(), hostFromHostSupplier.getIpAddress(), - hostFromTokenMapSupplier.getPort(), hostFromTokenMapSupplier.getSecurePort(), hostFromTokenMapSupplier.getRack(), - hostFromTokenMapSupplier.getDatacenter(), Host.Status.Up, hostFromTokenMapSupplier.getHashtag(), - hostFromTokenMapSupplier.getPassword())); + hostsUpFromHostSupplier.add(Host.clone(hostFromTokenMapSupplier).setStatus(Host.Status.Up)); allHostSetFromTokenMapSupplier.remove(hostFromTokenMapSupplier); } else { Host hostFromTokenMapSupplier = allHostSetFromTokenMapSupplier.get(hostFromHostSupplier); - hostsDownFromHostSupplier.add(new Host(hostFromHostSupplier.getHostName(), hostFromHostSupplier.getIpAddress(), - hostFromTokenMapSupplier.getPort(), hostFromTokenMapSupplier.getSecurePort(), hostFromTokenMapSupplier.getRack(), - hostFromTokenMapSupplier.getDatacenter(), Host.Status.Down, hostFromTokenMapSupplier.getHashtag(), - hostFromTokenMapSupplier.getPassword())); + hostsDownFromHostSupplier.add(Host.clone(hostFromTokenMapSupplier).setStatus(Host.Status.Down)); allHostSetFromTokenMapSupplier.remove(hostFromTokenMapSupplier); } } @@ -129,9 +129,7 @@ public HostStatusTracker refreshHosts() { // if a node is down, it might be absent in hostSupplier but has its presence in TokenMapSupplier. // Add that host to the down list here. for (Host h : allHostSetFromTokenMapSupplier.keySet()) { - hostsDownFromHostSupplier.add(new Host(h.getHostName(), h.getIpAddress(), - h.getPort(), h.getSecurePort(), h.getRack(), - h.getDatacenter(), Host.Status.Down, h.getHashtag())); + hostsDownFromHostSupplier.add(Host.clone(h).setStatus(Host.Status.Down)); } diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImpl.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImpl.java index 4679ce65..83cd9917 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImpl.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImpl.java @@ -15,15 +15,6 @@ */ package com.netflix.dyno.connectionpool.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.netflix.dyno.connectionpool.Connection; import com.netflix.dyno.connectionpool.ConnectionFactory; import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration; @@ -33,6 +24,14 @@ import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; import com.netflix.dyno.connectionpool.impl.lb.CircularList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; public class SimpleAsyncConnectionPoolImpl implements HostConnectionPool { @@ -209,7 +208,7 @@ public int getSocketTimeout() { private Connection createConnection() throws DynoException { - Connection connection = connFactory.createConnection((HostConnectionPool) this, null); + Connection connection = connFactory.createConnection(this); connMap.put(connection, connection); connection.open(); rrSelector.addElement(connection); diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplier.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplier.java index ac1c2bd1..3d17a68a 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplier.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplier.java @@ -17,6 +17,7 @@ import java.util.*; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.exception.TimeoutException; import com.netflix.dyno.connectionpool.impl.utils.ConfigUtils; import org.json.simple.JSONArray; @@ -240,7 +241,7 @@ List parseTokenListFromJson(String json) { securePort = Integer.valueOf(securePortStr); } - Host host = new Host(hostname, ipAddress, port, securePort, zone, datacenter, Status.Up, hashtag); + Host host = new HostBuilder().setHostname(hostname).setIpAddress(ipAddress).setPort(port).setSecurePort(securePort).setRack(zone).setDatacenter(datacenter).setStatus(Status.Up).setHashtag(hashtag).createHost(); if (isLocalDatacenterHost(host)) { HostToken hostToken = new HostToken(token, host); diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallback.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallback.java index ad6a6805..44ef7955 100644 --- a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallback.java +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallback.java @@ -15,8 +15,18 @@ ******************************************************************************/ package com.netflix.dyno.connectionpool.impl.lb; -import com.netflix.dyno.connectionpool.*; +import com.netflix.dyno.connectionpool.BaseOperation; +import com.netflix.dyno.connectionpool.Connection; +import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration; import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration.LoadBalancingStrategy; +import com.netflix.dyno.connectionpool.ConnectionPoolMonitor; +import com.netflix.dyno.connectionpool.HashPartitioner; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostConnectionPool; +import com.netflix.dyno.connectionpool.RetryPolicy; +import com.netflix.dyno.connectionpool.TokenMapSupplier; +import com.netflix.dyno.connectionpool.TokenPoolTopology; +import com.netflix.dyno.connectionpool.TokenRackMapper; import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; import com.netflix.dyno.connectionpool.exception.NoAvailableHostsException; @@ -34,7 +44,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -71,9 +80,9 @@ public class HostSelectionWithFallback { // The selector for the local zone private final HostSelectionStrategy localSelector; // Track selectors for each remote zone - private final ConcurrentHashMap> remoteRackSelectors = new ConcurrentHashMap>(); + private final ConcurrentHashMap> remoteRackSelectors = new ConcurrentHashMap<>(); - private final ConcurrentHashMap hostTokens = new ConcurrentHashMap(); + private final ConcurrentHashMap hostTokens = new ConcurrentHashMap<>(); private final TokenMapSupplier tokenSupplier; private final ConnectionPoolConfiguration cpConfig; @@ -86,7 +95,7 @@ public class HostSelectionWithFallback { private final AtomicReference topology = new AtomicReference<>(null); // list of names of remote zones. Used for RoundRobin over remote zones when local zone host is down - private final CircularList remoteDCNames = new CircularList(new ArrayList()); + private final CircularList remoteDCNames = new CircularList<>(new ArrayList<>()); private final HostSelectionStrategyFactory selectorFactory; @@ -356,7 +365,7 @@ public void initWithHosts(Map> hPools) { Map> localPools = getHostPoolsForRack(tokenPoolMap, localRack); localSelector.initWithHosts(localPools); if (localSelector.isTokenAware()) { - replicationFactor.set(calculateReplicationFactorForDC(allHostTokens, cpConfig.getLocalDataCenter())); + replicationFactor.set(HostUtils.calculateReplicationFactorForDC(allHostTokens, cpConfig.getLocalDataCenter(), localRack)); } // Initialize Remote selectors @@ -381,57 +390,7 @@ public void initWithHosts(Map> hPools) { * @return replicationFactor */ int calculateReplicationFactor(List allHostTokens) { - return calculateReplicationFactorForDC(allHostTokens, null); - } - - /** - * Calculate replication factor for a datacenter. - * If datacenter is null we use one of the hosts from the list and use its DC. - * - * @param allHostTokens - * @param dataCenter - * @return replicationFactor for the dataCenter - */ - int calculateReplicationFactorForDC(List allHostTokens, String dataCenter) { - Map groups = new HashMap<>(); - - Set uniqueHostTokens = new HashSet<>(allHostTokens); - if (dataCenter == null) { - if (localRack != null) { - dataCenter = localRack.substring(0, localRack.length() - 1); - } else { - // No DC specified. Get the DC from the first host and use its replication factor - Host host = allHostTokens.get(0).getHost(); - dataCenter = host.getRack().substring(0, host.getRack().length() - 1); - } - } - - for (HostToken hostToken : uniqueHostTokens) { - if (hostToken.getHost().getRack().contains(dataCenter)) { - Long token = hostToken.getToken(); - if (groups.containsKey(token)) { - int current = groups.get(token); - groups.put(token, current + 1); - } else { - groups.put(token, 1); - } - } - } - - Set uniqueCounts = new HashSet<>(groups.values()); - - if (uniqueCounts.size() > 1) { - throw new RuntimeException("Invalid configuration - replication factor cannot be asymmetric"); - } - - int rf = uniqueCounts.toArray(new Integer[uniqueCounts.size()])[0]; - - if (rf > 3) { - logger.warn("Replication Factor is high: " + uniqueHostTokens); - } - - return rf; - + return HostUtils.calculateReplicationFactorForDC(allHostTokens, null, localRack); } public void addHost(Host host, HostConnectionPool hostPool) { diff --git a/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostUtils.java b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostUtils.java new file mode 100644 index 00000000..54625ed6 --- /dev/null +++ b/dyno-core/src/main/java/com/netflix/dyno/connectionpool/impl/lb/HostUtils.java @@ -0,0 +1,80 @@ +package com.netflix.dyno.connectionpool.impl.lb; + +import com.netflix.dyno.connectionpool.Host; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class HostUtils { + + + private static final Logger logger = LoggerFactory.getLogger(HostSelectionWithFallback.class); + + /** + * Calculate replication factor from the given list of hosts + * + * @param allHostTokens + * @param localRack + * @return replicationFactor + */ + public static int calculateReplicationFactor(List allHostTokens, String localRack) { + return calculateReplicationFactorForDC(allHostTokens, null, localRack); + } + + /** + * Calculate replication factor for a datacenter. + * If datacenter is null we use one of the hosts from the list and use its DC. + * + * @param allHostTokens + * @param dataCenter + * @param localRack + * @return replicationFactor for the dataCenter + */ + public static int calculateReplicationFactorForDC(List allHostTokens, String dataCenter, String localRack) { + Map groups = new HashMap<>(); + + Set uniqueHostTokens = new HashSet<>(allHostTokens); + if (dataCenter == null) { + if (localRack != null) { + dataCenter = localRack.substring(0, localRack.length() - 1); + } else { + // No DC specified. Get the DC from the first host and use its replication factor + Host host = allHostTokens.get(0).getHost(); + String curRack = host.getRack(); + dataCenter = curRack.substring(0, curRack.length() - 1); + } + } + + for (HostToken hostToken : uniqueHostTokens) { + if (hostToken.getHost().getRack().contains(dataCenter)) { + Long token = hostToken.getToken(); + if (groups.containsKey(token)) { + int current = groups.get(token); + groups.put(token, current + 1); + } else { + groups.put(token, 1); + } + } + } + + Set uniqueCounts = new HashSet<>(groups.values()); + + if (uniqueCounts.size() > 1) { + throw new RuntimeException("Invalid configuration - replication factor cannot be asymmetric"); + } + + int rf = uniqueCounts.toArray(new Integer[uniqueCounts.size()])[0]; + + if (rf > 3) { + logger.warn("Replication Factor is high: " + uniqueHostTokens); + } + + return rf; + + } +} diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImplTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImplTest.java index 7940b699..b7084716 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImplTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/ConnectionPoolImplTest.java @@ -15,29 +15,14 @@ */ package com.netflix.dyno.connectionpool.impl; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; -import com.netflix.dyno.connectionpool.exception.PoolOfflineException; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - import com.netflix.dyno.connectionpool.AsyncOperation; import com.netflix.dyno.connectionpool.Connection; import com.netflix.dyno.connectionpool.ConnectionContext; import com.netflix.dyno.connectionpool.ConnectionFactory; -import com.netflix.dyno.connectionpool.ConnectionObservor; import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration.LoadBalancingStrategy; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.Host.Status; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.HostConnectionStats; import com.netflix.dyno.connectionpool.HostSupplier; @@ -51,10 +36,29 @@ import com.netflix.dyno.connectionpool.exception.DynoException; import com.netflix.dyno.connectionpool.exception.FatalConnectionException; import com.netflix.dyno.connectionpool.exception.NoAvailableHostsException; +import com.netflix.dyno.connectionpool.exception.PoolExhaustedException; +import com.netflix.dyno.connectionpool.exception.PoolOfflineException; import com.netflix.dyno.connectionpool.exception.PoolTimeoutException; import com.netflix.dyno.connectionpool.exception.ThrottledException; import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl.ErrorRateMonitorConfigImpl; import com.netflix.dyno.connectionpool.impl.lb.HostToken; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class ConnectionPoolImplTest { @@ -137,19 +141,24 @@ public ConnectionContext getContext() { private static ConnectionFactory connFactory = new ConnectionFactory() { @Override - public Connection createConnection(HostConnectionPool pool, ConnectionObservor observor) throws DynoConnectException, ThrottledException { + public Connection createConnection(HostConnectionPool pool) throws DynoConnectException, ThrottledException { return new TestConnection(pool); } + + @Override + public Connection createConnectionWithDataStore(HostConnectionPool pool) throws DynoConnectException { + return null; + } }; - private Host host1 = new Host("host1", 8080, "localRack", Status.Up); - private Host host2 = new Host("host2", 8080, "localRack", Status.Up); - private Host host3 = new Host("host3", 8080, "localRack", Status.Up); + private Host host1 = new HostBuilder().setHostname("host1").setPort(8080).setRack("localRack").setStatus(Status.Up).createHost(); + private Host host2 = new HostBuilder().setHostname("host2").setPort(8080).setRack("localRack").setStatus(Status.Up).createHost(); + private Host host3 = new HostBuilder().setHostname("host3").setPort(8080).setRack("localRack").setStatus(Status.Up).createHost(); // Used for Cross Rack fallback testing - private Host host4 = new Host("host4", 8080, "remoteRack", Status.Up); - private Host host5 = new Host("host5", 8080, "remoteRack", Status.Up); - private Host host6 = new Host("host6", 8080, "remoteRack", Status.Up); + private Host host4 = new HostBuilder().setHostname("host4").setPort(8080).setRack("remoteRack").setStatus(Status.Up).createHost(); + private Host host5 = new HostBuilder().setHostname("host5").setPort(8080).setRack("remoteRack").setStatus(Status.Up).createHost(); + private Host host6 = new HostBuilder().setHostname("host6").setPort(8080).setRack("remoteRack").setStatus(Status.Up).createHost(); private final List hostSupplierHosts = new ArrayList(); @@ -486,7 +495,7 @@ public void testHostEvictionDueToErrorRates() throws Exception { final ConnectionFactory badConnectionFactory = new ConnectionFactory() { @Override - public Connection createConnection(final HostConnectionPool pool, ConnectionObservor cObservor) throws DynoConnectException, ThrottledException { + public Connection createConnection(final HostConnectionPool pool) throws DynoConnectException { return new TestConnection(pool) { @@ -499,6 +508,11 @@ public OperationResult execute(Operation op) throws DynoEx } }; } + + @Override + public Connection createConnectionWithDataStore(HostConnectionPool pool) throws DynoConnectException { + return null; + } }; final ConnectionPoolImpl pool = new ConnectionPoolImpl(badConnectionFactory, cpConfig, cpMonitor); @@ -598,11 +612,11 @@ public Void call() throws Exception { } @Test - public void testWithRetries() throws Exception { + public void testWithRetries() { final ConnectionFactory badConnectionFactory = new ConnectionFactory() { @Override - public Connection createConnection(final HostConnectionPool pool, ConnectionObservor cObservor) throws DynoConnectException, ThrottledException { + public Connection createConnection(final HostConnectionPool pool) throws DynoConnectException { return new TestConnection(pool) { @Override public OperationResult execute(Operation op) throws DynoException { @@ -610,6 +624,11 @@ public OperationResult execute(Operation op) throws DynoEx } }; } + + @Override + public Connection createConnectionWithDataStore(HostConnectionPool pool) throws DynoConnectException { + return null; + } }; final RetryNTimes retry = new RetryNTimes(3, false); @@ -642,9 +661,9 @@ public void testHostsDownDuringStartup() { final ConnectionPoolImpl pool = new ConnectionPoolImpl(connFactory, cpConfig, cpMonitor); - hostSupplierHosts.add(new Host("host1_down", 8080, "localRack", Status.Down)); - hostSupplierHosts.add(new Host("host2_down", 8080, "localRack", Status.Down)); - hostSupplierHosts.add(new Host("host3_down", 8080, "localRack", Status.Down)); + hostSupplierHosts.add(new HostBuilder().setHostname("host1_down").setPort(8080).setRack("localRack").setStatus(Status.Down).createHost()); + hostSupplierHosts.add(new HostBuilder().setHostname("host2_down").setPort(8080).setRack("localRack").setStatus(Status.Down).createHost()); + hostSupplierHosts.add(new HostBuilder().setHostname("host3_down").setPort(8080).setRack("localRack").setStatus(Status.Down).createHost()); pool.start(); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitorTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitorTest.java index ae9418d8..8b3d2d73 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitorTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/CountingConnectionPoolMonitorTest.java @@ -15,6 +15,7 @@ */ package com.netflix.dyno.connectionpool.impl; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; @@ -30,8 +31,8 @@ public void testProcess() throws Exception { CountingConnectionPoolMonitor counter = new CountingConnectionPoolMonitor(); - Host host1 = new Host("host1", "address1", 1111, "rack1"); - Host host2 = new Host("host2", "address2", 2222, "rack1"); + Host host1 = new HostBuilder().setHostname("host1").setIpAddress("address1").setPort(1111).setRack("rack1").createHost(); + Host host2 = new HostBuilder().setHostname("host2").setIpAddress("address2").setPort(2222).setRack("rack1").createHost(); // Host 1 counter.incConnectionCreated(host1); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImplTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImplTest.java index 38ad8130..f9d4dc21 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImplTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostConnectionPoolImplTest.java @@ -15,27 +15,12 @@ */ package com.netflix.dyno.connectionpool.impl; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - import com.netflix.dyno.connectionpool.AsyncOperation; import com.netflix.dyno.connectionpool.Connection; import com.netflix.dyno.connectionpool.ConnectionContext; import com.netflix.dyno.connectionpool.ConnectionFactory; -import com.netflix.dyno.connectionpool.ConnectionObservor; import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.ListenableFuture; import com.netflix.dyno.connectionpool.Operation; @@ -43,11 +28,24 @@ import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; import com.netflix.dyno.connectionpool.exception.FatalConnectionException; -import com.netflix.dyno.connectionpool.exception.ThrottledException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; public class HostConnectionPoolImplTest { - private static final Host TestHost = new Host("TestHost", "TestAddress", 1234, "TestRack"); + private static final Host TestHost = new HostBuilder().setHostname("TestHost").setIpAddress("TestAddress").setPort(1234).setRack("TestRack").createHost(); // TEST UTILS SETUP private class TestClient { @@ -119,9 +117,14 @@ public ConnectionContext getContext() { private static ConnectionFactory connFactory = new ConnectionFactory() { @Override - public Connection createConnection(HostConnectionPool pool, ConnectionObservor cObservor) throws DynoConnectException, ThrottledException { + public Connection createConnection(HostConnectionPool pool) throws DynoConnectException { return new TestConnection(pool); } + + @Override + public Connection createConnectionWithDataStore(HostConnectionPool pool) throws DynoConnectException { + return null; + } }; private static ConnectionPoolConfigurationImpl config = new ConnectionPoolConfigurationImpl("TestClient"); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostStatusTrackerTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostStatusTrackerTest.java index 69329337..88858da8 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostStatusTrackerTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/HostStatusTrackerTest.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.Set; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; @@ -135,7 +136,7 @@ private Set getHostSet(String... names) { if (names != null && names.length > 0) { for (String name : names) { if (!name.isEmpty()) { - set.add(new Host(name, 1234, "r1")); + set.add(new HostBuilder().setHostname(name).setPort(1234).setRack("r1").createHost()); } } } diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/OperationResultImplTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/OperationResultImplTest.java index 209b8a13..7dc11a76 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/OperationResultImplTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/OperationResultImplTest.java @@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; @@ -30,7 +31,7 @@ public void testProcess() throws Exception { OperationMonitor monitor = new LastOperationMonitor(); OperationResultImpl opResult = new OperationResultImpl("test", 11, monitor); - Host host = new Host("testHost", "rand_ip", 1234, "rand_rack"); + Host host = new HostBuilder().setHostname("testHost").setIpAddress("rand_ip").setPort(1234).setRack("rand_rack").createHost(); opResult.attempts(2) .addMetadata("foo", "f1").addMetadata("bar", "b1") diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImplTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImplTest.java index 9947fc91..a5dc1db6 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImplTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/SimpleAsyncConnectionPoolImplTest.java @@ -15,7 +15,20 @@ */ package com.netflix.dyno.connectionpool.impl; -import static org.mockito.Mockito.mock; +import com.netflix.dyno.connectionpool.Connection; +import com.netflix.dyno.connectionpool.ConnectionFactory; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; +import com.netflix.dyno.connectionpool.HostConnectionPool; +import com.netflix.dyno.connectionpool.exception.DynoConnectException; +import com.netflix.dyno.connectionpool.exception.FatalConnectionException; +import com.netflix.dyno.connectionpool.exception.ThrottledException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -25,26 +38,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.netflix.dyno.connectionpool.Connection; -import com.netflix.dyno.connectionpool.ConnectionFactory; -import com.netflix.dyno.connectionpool.ConnectionObservor; -import com.netflix.dyno.connectionpool.Host; -import com.netflix.dyno.connectionpool.HostConnectionPool; -import com.netflix.dyno.connectionpool.exception.DynoConnectException; -import com.netflix.dyno.connectionpool.exception.FatalConnectionException; -import com.netflix.dyno.connectionpool.exception.ThrottledException; +import static org.mockito.Mockito.mock; public class SimpleAsyncConnectionPoolImplTest { // TEST UTILS SETUP - private static final Host TestHost = new Host("TestHost", "TestIp", 1234, "TestRack"); + private static final Host TestHost = new HostBuilder().setHostname("TestHost").setIpAddress("TestIp").setPort(1234).setRack("TestRack").createHost(); private class TestClient { } @@ -55,9 +54,14 @@ private class TestClient { private static ConnectionFactory connFactory = new ConnectionFactory() { @SuppressWarnings("unchecked") @Override - public Connection createConnection(HostConnectionPool pool, ConnectionObservor cObservor) throws DynoConnectException, ThrottledException { + public Connection createConnection(HostConnectionPool pool) throws DynoConnectException, ThrottledException { return mock(Connection.class); } + + @Override + public Connection createConnectionWithDataStore(HostConnectionPool pool) throws DynoConnectException { + return null; + } }; private static ConnectionPoolConfigurationImpl config = new ConnectionPoolConfigurationImpl("TestClient"); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/hash/BinarySearchTokenMapperTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/hash/BinarySearchTokenMapperTest.java index 80dde8e6..d76ef89a 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/hash/BinarySearchTokenMapperTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/hash/BinarySearchTokenMapperTest.java @@ -15,18 +15,17 @@ */ package com.netflix.dyno.connectionpool.impl.hash; +import com.netflix.dyno.connectionpool.Host.Status; +import com.netflix.dyno.connectionpool.HostBuilder; +import com.netflix.dyno.connectionpool.impl.lb.HostToken; +import org.junit.Assert; +import org.junit.Test; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.junit.Assert; -import org.junit.Test; - -import com.netflix.dyno.connectionpool.Host; -import com.netflix.dyno.connectionpool.Host.Status; -import com.netflix.dyno.connectionpool.impl.lb.HostToken; - public class BinarySearchTokenMapperTest { @Test @@ -69,7 +68,7 @@ public void testAddToken() throws Exception { // Now construct the midpoint token between 'h2' and 'h3' Long midpoint = 309687905L + (1383429731L - 309687905L) / 2; - tokenMapper.addHostToken(new HostToken(midpoint, new Host("h23", -1, "r1", Status.Up))); + tokenMapper.addHostToken(new HostToken(midpoint, new HostBuilder().setHostname("h23").setPort(-1).setRack("r1").setStatus(Status.Up).createHost())); failures += runTest(309687905L + 1L, 309687905L + 10L, "h23", tokenMapper); Assert.assertTrue("Failures: " + failures, failures == 0); @@ -93,7 +92,7 @@ public void testRemoveToken() throws Exception { Assert.assertTrue("Failures: " + failures, failures == 0); // Now remove token 'h3' - tokenMapper.remoteHostToken(new HostToken(1383429731L, new Host("h2", -1, "r1", Status.Up))); + tokenMapper.remoteHostToken(new HostToken(1383429731L, new HostBuilder().setHostname("h2").setPort(-1).setRack("r1").setStatus(Status.Up).createHost())); failures += runTest(309687905L + 1L, 309687905L + 10L, "h3", tokenMapper); Assert.assertTrue("Failures: " + failures, failures == 0); @@ -137,10 +136,10 @@ private Collection getTestTokens() { List tokens = new ArrayList(); - tokens.add(new HostToken(309687905L, new Host("h1", -1, "r1", Status.Up))); - tokens.add(new HostToken(1383429731L, new Host("h2", -1, "r1", Status.Up))); - tokens.add(new HostToken(2457171554L, new Host("h3", -1, "r1", Status.Up))); - tokens.add(new HostToken(3530913377L, new Host("h4", -1, "r1", Status.Up))); + tokens.add(new HostToken(309687905L, new HostBuilder().setHostname("h1").setPort(-1).setRack("r1").setStatus(Status.Up).createHost())); + tokens.add(new HostToken(1383429731L, new HostBuilder().setHostname("h2").setPort(-1).setRack("r1").setStatus(Status.Up).createHost())); + tokens.add(new HostToken(2457171554L, new HostBuilder().setHostname("h3").setPort(-1).setRack("r1").setStatus(Status.Up).createHost())); + tokens.add(new HostToken(3530913377L, new HostBuilder().setHostname("h4").setPort(-1).setRack("r1").setStatus(Status.Up).createHost())); return tokens; } diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/health/ConnectionPoolHealthTrackerTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/health/ConnectionPoolHealthTrackerTest.java index e7d6cfda..fea918c9 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/health/ConnectionPoolHealthTrackerTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/health/ConnectionPoolHealthTrackerTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import com.netflix.dyno.connectionpool.HostBuilder; import org.apache.log4j.BasicConfigurator; import org.junit.AfterClass; import org.junit.Assert; @@ -66,7 +67,7 @@ public void testConnectionPoolRecycle() throws Exception { ConnectionPoolHealthTracker tracker = new ConnectionPoolHealthTracker(config, threadPool, 1000, -1); tracker.start(); - Host h1 = new Host("h1", "r1", Status.Up); + Host h1 = new HostBuilder().setHostname("h1").setRack("r1").setStatus(Status.Up).createHost(); AtomicBoolean poolStatus = new AtomicBoolean(false); HostConnectionPool hostPool = getMockConnectionPool(h1, poolStatus); @@ -94,7 +95,7 @@ public void testBadConnectionPoolKeepsReconnecting() throws Exception { ConnectionPoolHealthTracker tracker = new ConnectionPoolHealthTracker(config, threadPool, 1000, -1); tracker.start(); - Host h1 = new Host("h1", "r1", Status.Up); + Host h1 = new HostBuilder().setHostname("h1").setRack("r1").setStatus(Status.Up).createHost(); AtomicBoolean poolStatus = new AtomicBoolean(false); HostConnectionPool hostPool = getMockConnectionPool(h1, poolStatus, true); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplierTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplierTest.java index f274e067..d23e85e1 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplierTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/AbstractTokenMapSupplierTest.java @@ -17,6 +17,7 @@ import java.util.*; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; @@ -53,14 +54,14 @@ public void testParseJson() throws Exception { List hostList = new ArrayList<>(); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-50-17-65-2.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-54-83-87-174.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-54-81-138-73.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-54-82-176-215.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-54-82-83-115.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-54-211-220-55.compute-1.amazonaws.com", "rack", Status.Up)); - hostList.add(new Host("ec2-54-80-65-203.compute-1.amazonaws.com", "rack", Status.Up)); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-50-17-65-2.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-83-87-174.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-81-138-73.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-176-215.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-83-115.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-211-220-55.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-80-65-203.compute-1.amazonaws.com").setRack("rack").setStatus(Status.Up).createHost()); List hTokens = testTokenMapSupplier.getTokens(new HashSet<>(hostList)); Collections.sort(hTokens, new Comparator() { diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallbackTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallbackTest.java index 92982717..eb4ceee8 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallbackTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostSelectionWithFallbackTest.java @@ -22,6 +22,7 @@ import com.netflix.dyno.connectionpool.HashPartitioner; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.Host.Status; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.RetryPolicy; import com.netflix.dyno.connectionpool.TokenMapSupplier; @@ -82,12 +83,12 @@ public byte[] getBinaryKey() { String rack2 = "us-east-1d"; String rack3 = "us-east-1e"; - Host h1 = new Host("h1", rack1, Status.Up); - Host h2 = new Host("h2", rack1, Status.Up); - Host h3 = new Host("h3", "remoteRack1", Status.Up); - Host h4 = new Host("h4", "remoteRack1", Status.Up); - Host h5 = new Host("h5", "remoteRack2", Status.Up); - Host h6 = new Host("h6", "remoteRack2", Status.Up); + Host h1 = new HostBuilder().setHostname("h1").setRack(rack1).setStatus(Status.Up).createHost(); + Host h2 = new HostBuilder().setHostname("h2").setRack(rack1).setStatus(Status.Up).createHost(); + Host h3 = new HostBuilder().setHostname("h3").setRack("remoteRack1").setStatus(Status.Up).createHost(); + Host h4 = new HostBuilder().setHostname("h4").setRack("remoteRack1").setStatus(Status.Up).createHost(); + Host h5 = new HostBuilder().setHostname("h5").setRack("remoteRack2").setStatus(Status.Up).createHost(); + Host h6 = new HostBuilder().setHostname("h6").setRack("remoteRack2").setStatus(Status.Up).createHost(); Host[] arr = {h1, h2, h3, h4, h5, h6}; List hosts = Arrays.asList(arr); @@ -400,21 +401,21 @@ public void testReplicationFactorOf3WithDupes() { HostSelectionWithFallback selection = new HostSelectionWithFallback(cpConfig, cpMonitor); List hostTokens = Arrays.asList( - new HostToken(1383429731L, new Host("host-1", -1, rack1)), // Use -1 otherwise the port is opened which works - new HostToken(2815085496L, new Host("host-2", -1, rack1)), - new HostToken(4246741261L, new Host("host-3", -1, rack1)), - new HostToken(1383429731L, new Host("host-4", -1, rack1)), - new HostToken(2815085496L, new Host("host-5", -1, rack1)), - new HostToken(4246741261L, new Host("host-6", -1, rack1)), - new HostToken(1383429731L, new Host("host-7", -1, rack1)), - new HostToken(2815085496L, new Host("host-8", -1, rack1)), - new HostToken(4246741261L, new Host("host-9", -1, rack1)), - new HostToken(1383429731L, new Host("host-7", -1, rack1)), - new HostToken(2815085496L, new Host("host-8", -1, rack1)), - new HostToken(4246741261L, new Host("host-9", -1, rack1)), - new HostToken(1383429731L, new Host("host-7", -1, rack1)), - new HostToken(2815085496L, new Host("host-8", -1, rack1)), - new HostToken(4246741261L, new Host("host-9", -1, rack1)) + new HostToken(1383429731L, new HostBuilder().setHostname("host-1").setPort(-1).setRack(rack1).createHost()), // Use -1 otherwise the port is opened which works + new HostToken(2815085496L, new HostBuilder().setHostname("host-2").setPort(-1).setRack(rack1).createHost()), + new HostToken(4246741261L, new HostBuilder().setHostname("host-3").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-4").setPort(-1).setRack(rack1).createHost()), + new HostToken(2815085496L, new HostBuilder().setHostname("host-5").setPort(-1).setRack(rack1).createHost()), + new HostToken(4246741261L, new HostBuilder().setHostname("host-6").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-7").setPort(-1).setRack(rack1).createHost()), + new HostToken(2815085496L, new HostBuilder().setHostname("host-8").setPort(-1).setRack(rack1).createHost()), + new HostToken(4246741261L, new HostBuilder().setHostname("host-9").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-7").setPort(-1).setRack(rack1).createHost()), + new HostToken(2815085496L, new HostBuilder().setHostname("host-8").setPort(-1).setRack(rack1).createHost()), + new HostToken(4246741261L, new HostBuilder().setHostname("host-9").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-7").setPort(-1).setRack(rack1).createHost()), + new HostToken(2815085496L, new HostBuilder().setHostname("host-8").setPort(-1).setRack(rack1).createHost()), + new HostToken(4246741261L, new HostBuilder().setHostname("host-9").setPort(-1).setRack(rack1).createHost()) ); @@ -432,12 +433,12 @@ public void testReplicationFactorOf3() { HostSelectionWithFallback selection = new HostSelectionWithFallback(cpConfig, cpMonitor); List hostTokens = Arrays.asList( - new HostToken(1111L, new Host("host-1", -1, rack1)), - new HostToken(1111L, new Host("host-2", -1, rack1)), - new HostToken(1111L, new Host("host-3", -1, rack1)), - new HostToken(2222L, new Host("host-4", -1, rack1)), - new HostToken(2222L, new Host("host-5", -1, rack1)), - new HostToken(2222L, new Host("host-6", -1, rack1)) + new HostToken(1111L, new HostBuilder().setHostname("host-1").setPort(-1).setRack(rack1).createHost()), + new HostToken(1111L, new HostBuilder().setHostname("host-2").setPort(-1).setRack(rack1).createHost()), + new HostToken(1111L, new HostBuilder().setHostname("host-3").setPort(-1).setRack(rack1).createHost()), + new HostToken(2222L, new HostBuilder().setHostname("host-4").setPort(-1).setRack(rack1).createHost()), + new HostToken(2222L, new HostBuilder().setHostname("host-5").setPort(-1).setRack(rack1).createHost()), + new HostToken(2222L, new HostBuilder().setHostname("host-6").setPort(-1).setRack(rack1).createHost()) ); int rf = selection.calculateReplicationFactor(hostTokens); @@ -454,10 +455,10 @@ public void testReplicationFactorOf2() { HostSelectionWithFallback selection = new HostSelectionWithFallback(cpConfig, cpMonitor); List hostTokens = Arrays.asList( - new HostToken(1111L, new Host("host-1", -1, rack1)), - new HostToken(1111L, new Host("host-2", -1, rack1)), - new HostToken(2222L, new Host("host-4", -1, rack1)), - new HostToken(2222L, new Host("host-5", -1, rack1)) + new HostToken(1111L, new HostBuilder().setHostname("host-1").setPort(-1).setRack(rack1).createHost()), + new HostToken(1111L, new HostBuilder().setHostname("host-2").setPort(-1).setRack(rack1).createHost()), + new HostToken(2222L, new HostBuilder().setHostname("host-4").setPort(-1).setRack(rack1).createHost()), + new HostToken(2222L, new HostBuilder().setHostname("host-5").setPort(-1).setRack(rack1).createHost()) ); int rf = selection.calculateReplicationFactor(hostTokens); @@ -493,9 +494,9 @@ public void testIllegalReplicationFactor() { HostSelectionWithFallback selection = new HostSelectionWithFallback(cpConfig, cpMonitor); List hostTokens = Arrays.asList( - new HostToken(1111L, new Host("host-1", -1, rack1)), - new HostToken(1111L, new Host("host-2", -1, rack1)), - new HostToken(2222L, new Host("host-4", -1, rack1)) + new HostToken(1111L, new HostBuilder().setHostname("host-1").setPort(-1).setRack(rack1).createHost()), + new HostToken(1111L, new HostBuilder().setHostname("host-2").setPort(-1).setRack(rack1).createHost()), + new HostToken(2222L, new HostBuilder().setHostname("host-4").setPort(-1).setRack(rack1).createHost()) ); selection.calculateReplicationFactor(hostTokens); @@ -511,26 +512,26 @@ public void testReplicationFactorForMultiRegionCluster() { HostSelectionWithFallback selection = new HostSelectionWithFallback(cpConfig, cpMonitor); List hostTokens = Arrays.asList( - new HostToken(3530913378L, new Host("host-1", -1, rack1)), - new HostToken(1383429731L, new Host("host-1", -1, rack1)), - new HostToken(3530913378L, new Host("host-2", -1, rack1)), - new HostToken(1383429731L, new Host("host-2", -1, rack1)), - new HostToken(1383429731L, new Host("host-3", -1, rack1)), - new HostToken(3530913378L, new Host("host-3", -1, rack1)), - - new HostToken(3530913378L, new Host("host-4", -1, "remoteRack1")), - new HostToken(1383429731L, new Host("host-4", -1, "remoteRack1")), - new HostToken(3530913378L, new Host("host-5", -1, "remoteRack1")), - new HostToken(1383429731L, new Host("host-5", -1, "remoteRack1")), - new HostToken(3530913378L, new Host("host-6", -1, "remoteRack1")), - new HostToken(1383429731L, new Host("host-6", -1, "remoteRack1")), - - new HostToken(3530913378L, new Host("host-7", -1, "remoteRack2")), - new HostToken(1383429731L, new Host("host-7", -1, "remoteRack2")), - new HostToken(1383429731L, new Host("host-8", -1, "remoteRack2")), - new HostToken(3530913378L, new Host("host-8", -1, "remoteRack1")), - new HostToken(3530913378L, new Host("host-9", -1, "remoteRack2")), - new HostToken(1383429731L, new Host("host-9", -1, "remoteRack2")) + new HostToken(3530913378L, new HostBuilder().setHostname("host-1").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-1").setPort(-1).setRack(rack1).createHost()), + new HostToken(3530913378L, new HostBuilder().setHostname("host-2").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-2").setPort(-1).setRack(rack1).createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-3").setPort(-1).setRack(rack1).createHost()), + new HostToken(3530913378L, new HostBuilder().setHostname("host-3").setPort(-1).setRack(rack1).createHost()), + + new HostToken(3530913378L, new HostBuilder().setHostname("host-4").setPort(-1).setRack("remoteRack1").createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-4").setPort(-1).setRack("remoteRack1").createHost()), + new HostToken(3530913378L, new HostBuilder().setHostname("host-5").setPort(-1).setRack("remoteRack1").createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-5").setPort(-1).setRack("remoteRack1").createHost()), + new HostToken(3530913378L, new HostBuilder().setHostname("host-6").setPort(-1).setRack("remoteRack1").createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-6").setPort(-1).setRack("remoteRack1").createHost()), + + new HostToken(3530913378L, new HostBuilder().setHostname("host-7").setPort(-1).setRack("remoteRack2").createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-7").setPort(-1).setRack("remoteRack2").createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-8").setPort(-1).setRack("remoteRack2").createHost()), + new HostToken(3530913378L, new HostBuilder().setHostname("host-8").setPort(-1).setRack("remoteRack1").createHost()), + new HostToken(3530913378L, new HostBuilder().setHostname("host-9").setPort(-1).setRack("remoteRack2").createHost()), + new HostToken(1383429731L, new HostBuilder().setHostname("host-9").setPort(-1).setRack("remoteRack2").createHost()) ); int rf = selection.calculateReplicationFactor(hostTokens); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostTokenTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostTokenTest.java index 1db4ffc2..07947cf3 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostTokenTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/HostTokenTest.java @@ -20,38 +20,37 @@ import java.util.Comparator; import java.util.List; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; -import com.netflix.dyno.connectionpool.Host; - public class HostTokenTest { @Test public void testEquals() throws Exception { - HostToken t1 = new HostToken(1L, new Host("foo", 1234, "foo_rack")); - HostToken t2 = new HostToken(1L, new Host("foo", 1234, "foo_rack")); + HostToken t1 = new HostToken(1L, new HostBuilder().setHostname("foo").setPort(1234).setRack("foo_rack").createHost()); + HostToken t2 = new HostToken(1L, new HostBuilder().setHostname("foo").setPort(1234).setRack("foo_rack").createHost()); Assert.assertEquals(t1, t2); // change token - HostToken t3 = new HostToken(2L, new Host("foo", 1234, "foo_rack")); + HostToken t3 = new HostToken(2L, new HostBuilder().setHostname("foo").setPort(1234).setRack("foo_rack").createHost()); Assert.assertFalse(t1.equals(t3)); // change host name - HostToken t4 = new HostToken(1L, new Host("foo1", 1234, "foo_rack")); + HostToken t4 = new HostToken(1L, new HostBuilder().setHostname("foo1").setPort(1234).setRack("foo_rack").createHost()); Assert.assertFalse(t1.equals(t4)); } @Test public void testSort() throws Exception { - HostToken t1 = new HostToken(1L, new Host("foo1", 1234, "foo_rack")); - HostToken t2 = new HostToken(2L, new Host("foo2", 1234, "foo_rack")); - HostToken t3 = new HostToken(3L, new Host("foo3", 1234, "foo_rack")); - HostToken t4 = new HostToken(4L, new Host("foo4", 1234, "foo_rack")); - HostToken t5 = new HostToken(5L, new Host("foo5", 1234, "foo_rack")); + HostToken t1 = new HostToken(1L, new HostBuilder().setHostname("foo1").setPort(1234).setRack("foo_rack").createHost()); + HostToken t2 = new HostToken(2L, new HostBuilder().setHostname("foo2").setPort(1234).setRack("foo_rack").createHost()); + HostToken t3 = new HostToken(3L, new HostBuilder().setHostname("foo3").setPort(1234).setRack("foo_rack").createHost()); + HostToken t4 = new HostToken(4L, new HostBuilder().setHostname("foo4").setPort(1234).setRack("foo_rack").createHost()); + HostToken t5 = new HostToken(5L, new HostBuilder().setHostname("foo5").setPort(1234).setRack("foo_rack").createHost()); HostToken[] arr = {t5, t2, t4, t3, t1}; List list = Arrays.asList(arr); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/RoundRobinSelectionTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/RoundRobinSelectionTest.java index cc150e97..6b0422c9 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/RoundRobinSelectionTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/RoundRobinSelectionTest.java @@ -23,11 +23,11 @@ import java.util.Map; import java.util.TreeMap; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; import com.netflix.dyno.connectionpool.BaseOperation; -import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.Host.Status; import com.netflix.dyno.connectionpool.HostConnectionPool; @@ -51,10 +51,10 @@ public class RoundRobinSelectionTest { * cqlsh:dyno_bootstrap> */ - private final HostToken h1 = new HostToken(309687905L, new Host("h1", -1, "r1", Status.Up)); - private final HostToken h2 = new HostToken(1383429731L, new Host("h2", -1, "r1", Status.Up)); - private final HostToken h3 = new HostToken(2457171554L, new Host("h3", -1, "r1", Status.Up)); - private final HostToken h4 = new HostToken(3530913377L, new Host("h4", -1, "r1", Status.Up)); + private final HostToken h1 = new HostToken(309687905L, new HostBuilder().setHostname("h1").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h2 = new HostToken(1383429731L, new HostBuilder().setHostname("h2").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h3 = new HostToken(2457171554L, new HostBuilder().setHostname("h3").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h4 = new HostToken(3530913377L, new HostBuilder().setHostname("h4").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); private final BaseOperation testOperation = new BaseOperation() { diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionBinaryTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionBinaryTest.java index 43c3926b..f13fe091 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionBinaryTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionBinaryTest.java @@ -26,11 +26,11 @@ import java.util.Map; import java.util.TreeMap; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; import com.netflix.dyno.connectionpool.BaseOperation; -import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.Host.Status; import com.netflix.dyno.connectionpool.impl.hash.Murmur1HashPartitioner; @@ -59,15 +59,15 @@ public class TokenAwareSelectionBinaryTest { private static final String UTF_8 = "UTF-8"; private static final Charset charset = Charset.forName(UTF_8); - private final HostToken h1 = new HostToken(309687905L, new Host("h1", -1, "r1", Status.Up)); - private final HostToken h2 = new HostToken(1383429731L, new Host("h2", -1, "r1", Status.Up)); - private final HostToken h3 = new HostToken(2457171554L, new Host("h3", -1, "r1", Status.Up)); - private final HostToken h4 = new HostToken(3530913377L, new Host("h4", -1, "r1", Status.Up)); + private final HostToken h1 = new HostToken(309687905L, new HostBuilder().setHostname("h1").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h2 = new HostToken(1383429731L, new HostBuilder().setHostname("h2").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h3 = new HostToken(2457171554L, new HostBuilder().setHostname("h3").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h4 = new HostToken(3530913377L, new HostBuilder().setHostname("h4").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); - private final HostToken h1p8100 = new HostToken(309687905L, new Host("h1", 8100, "r1", Status.Up)); - private final HostToken h1p8101 = new HostToken(1383429731L, new Host("h1", 8101, "r1", Status.Up)); - private final HostToken h1p8102 = new HostToken(2457171554L, new Host("h1", 8102, "r1", Status.Up)); - private final HostToken h1p8103 = new HostToken(3530913377L, new Host("h1", 8103, "r1", Status.Up)); + private final HostToken h1p8100 = new HostToken(309687905L, new HostBuilder().setHostname("h1").setPort(8100).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h1p8101 = new HostToken(1383429731L, new HostBuilder().setHostname("h1").setPort(8101).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h1p8102 = new HostToken(2457171554L, new HostBuilder().setHostname("h1").setPort(8102).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h1p8103 = new HostToken(3530913377L, new HostBuilder().setHostname("h1").setPort(8103).setRack("r1").setStatus(Status.Up).createHost()); private final Murmur1HashPartitioner m1Hash = new Murmur1HashPartitioner(); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionHastagTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionHastagTest.java index 3182c04d..4481be90 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionHastagTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionHastagTest.java @@ -24,11 +24,11 @@ import java.util.Map; import java.util.TreeMap; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; import com.netflix.dyno.connectionpool.BaseOperation; -import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.Host.Status; import com.netflix.dyno.connectionpool.impl.hash.Murmur1HashPartitioner; @@ -41,20 +41,20 @@ */ public class TokenAwareSelectionHastagTest { - private final HostToken host1 = new HostToken(309687905L, new Host("host1", -1, "r1", Status.Up, "{}")); - private final HostToken host2 = new HostToken(1383429731L, new Host("host2", -1, "r1", Status.Up, "{}")); - private final HostToken host3 = new HostToken(2457171554L, new Host("host3", -1, "r1", Status.Up, "{}")); - private final HostToken host4 = new HostToken(3530913377L, new Host("host4", -1, "r1", Status.Up, "{}")); + private final HostToken host1 = new HostToken(309687905L, new HostBuilder().setHostname("host1").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("{}").createHost()); + private final HostToken host2 = new HostToken(1383429731L, new HostBuilder().setHostname("host2").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("{}").createHost()); + private final HostToken host3 = new HostToken(2457171554L, new HostBuilder().setHostname("host3").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("{}").createHost()); + private final HostToken host4 = new HostToken(3530913377L, new HostBuilder().setHostname("host4").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("{}").createHost()); - private final HostToken host5 = new HostToken(309687905L, new Host("host5", -1, "r1", Status.Up, "")); - private final HostToken host6 = new HostToken(1383429731L, new Host("host6", -1, "r1", Status.Up, "")); - private final HostToken host7 = new HostToken(2457171554L, new Host("host7", -1, "r1", Status.Up, "")); - private final HostToken host8 = new HostToken(3530913377L, new Host("host8", -1, "r1", Status.Up, "")); + private final HostToken host5 = new HostToken(309687905L, new HostBuilder().setHostname("host5").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("").createHost()); + private final HostToken host6 = new HostToken(1383429731L, new HostBuilder().setHostname("host6").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("").createHost()); + private final HostToken host7 = new HostToken(2457171554L, new HostBuilder().setHostname("host7").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("").createHost()); + private final HostToken host8 = new HostToken(3530913377L, new HostBuilder().setHostname("host8").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("").createHost()); - private final HostToken host9 = new HostToken(309687905L, new Host("host9", -1, "r1", Status.Up, "[]")); - private final HostToken host10 = new HostToken(1383429731L, new Host("host10", -1, "r1", Status.Up, "{}")); - private final HostToken host11 = new HostToken(2457171554L, new Host("host11", -1, "r1", Status.Up, "//")); - private final HostToken host12 = new HostToken(3530913377L, new Host("host12", -1, "r1", Status.Up, "--")); + private final HostToken host9 = new HostToken(309687905L, new HostBuilder().setHostname("host9").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("[]").createHost()); + private final HostToken host10 = new HostToken(1383429731L, new HostBuilder().setHostname("host10").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("{}").createHost()); + private final HostToken host11 = new HostToken(2457171554L, new HostBuilder().setHostname("host11").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("//").createHost()); + private final HostToken host12 = new HostToken(3530913377L, new HostBuilder().setHostname("host12").setPort(-1).setRack("r1").setStatus(Status.Up).setHashtag("--").createHost()); private final Murmur1HashPartitioner m1Hash = new Murmur1HashPartitioner(); String hashValue = "bar"; diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionTest.java index bacaa8ad..d1d36c77 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenAwareSelectionTest.java @@ -24,11 +24,11 @@ import java.util.Map; import java.util.TreeMap; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; import com.netflix.dyno.connectionpool.BaseOperation; -import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.Host.Status; import com.netflix.dyno.connectionpool.impl.hash.Murmur1HashPartitioner; @@ -52,16 +52,16 @@ public class TokenAwareSelectionTest { cqlsh:dyno_bootstrap> */ - private final HostToken h1 = new HostToken(309687905L, new Host("h1", -1, "r1", Status.Up)); - private final HostToken h2 = new HostToken(1383429731L, new Host("h2", -1, "r1", Status.Up)); - private final HostToken h3 = new HostToken(2457171554L, new Host("h3", -1, "r1", Status.Up)); - private final HostToken h4 = new HostToken(3530913377L, new Host("h4", -1, "r1", Status.Up)); + private final HostToken h1 = new HostToken(309687905L, new HostBuilder().setHostname("h1").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h2 = new HostToken(1383429731L, new HostBuilder().setHostname("h2").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h3 = new HostToken(2457171554L, new HostBuilder().setHostname("h3").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h4 = new HostToken(3530913377L, new HostBuilder().setHostname("h4").setPort(-1).setRack("r1").setStatus(Status.Up).createHost()); - private final HostToken h1p8100 = new HostToken(309687905L, new Host("h1", 8100, "r1", Status.Up)); - private final HostToken h1p8101 = new HostToken(1383429731L, new Host("h1", 8101, "r1", Status.Up)); - private final HostToken h1p8102 = new HostToken(2457171554L, new Host("h1", 8102, "r1", Status.Up)); - private final HostToken h1p8103 = new HostToken(3530913377L, new Host("h1", 8103, "r1", Status.Up)); + private final HostToken h1p8100 = new HostToken(309687905L, new HostBuilder().setHostname("h1").setPort(8100).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h1p8101 = new HostToken(1383429731L, new HostBuilder().setHostname("h1").setPort(8101).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h1p8102 = new HostToken(2457171554L, new HostBuilder().setHostname("h1").setPort(8102).setRack("r1").setStatus(Status.Up).createHost()); + private final HostToken h1p8103 = new HostToken(3530913377L, new HostBuilder().setHostname("h1").setPort(8103).setRack("r1").setStatus(Status.Up).createHost()); private final Murmur1HashPartitioner m1Hash = new Murmur1HashPartitioner(); diff --git a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenMapSupplierTest.java b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenMapSupplierTest.java index f72c9782..883ea436 100644 --- a/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenMapSupplierTest.java +++ b/dyno-core/src/test/java/com/netflix/dyno/connectionpool/impl/lb/TokenMapSupplierTest.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import com.netflix.dyno.connectionpool.HostBuilder; import org.junit.Assert; import org.junit.Test; @@ -40,14 +41,14 @@ public void testParseJson() throws Exception { List hostList = new ArrayList(); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", 11211, "us-east-1d", Status.Up)); - hostList.add(new Host("ec2-50-17-65-2.compute-1.amazonaws.com", 11211, "us-east-1d", Status.Up)); - hostList.add(new Host("ec2-54-83-87-174.compute-1.amazonaws.com", 11211, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-81-138-73.compute-1.amazonaws.com", 11211, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-82-176-215.compute-1.amazonaws.com", 11211, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-82-83-115.compute-1.amazonaws.com", 11211, "us-east-1e", Status.Up)); - hostList.add(new Host("ec2-54-211-220-55.compute-1.amazonaws.com", 11211, "us-east-1e", Status.Up)); - hostList.add(new Host("ec2-54-80-65-203.compute-1.amazonaws.com", 11211, "us-east-1e", Status.Up)); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1d").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-50-17-65-2.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1d").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-83-87-174.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-81-138-73.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-176-215.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-83-115.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1e").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-211-220-55.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1e").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-80-65-203.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1e").setStatus(Status.Up).createHost()); HttpEndpointBasedTokenMapSupplier tokenSupplier = new HttpEndpointBasedTokenMapSupplier("us-east-1d", 11211); @@ -85,14 +86,14 @@ public void testParseJsonWithPorts() throws Exception { List hostList = new ArrayList(); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", 11211, "us-east-1d", Status.Up)); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", 11212, "us-east-1d", Status.Up)); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", 11213, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", 11214, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-82-176-215.compute-1.amazonaws.com", 11215, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-82-83-115.compute-1.amazonaws.com", 11216, "us-east-1e", Status.Up)); - hostList.add(new Host("ec2-54-211-220-55.compute-1.amazonaws.com", 11217, "us-east-1e", Status.Up)); - hostList.add(new Host("ec2-54-80-65-203.compute-1.amazonaws.com", 11218, "us-east-1e", Status.Up)); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1d").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setPort(11212).setRack("us-east-1d").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setPort(11213).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setPort(11214).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-176-215.compute-1.amazonaws.com").setPort(11215).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-83-115.compute-1.amazonaws.com").setPort(11216).setRack("us-east-1e").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-211-220-55.compute-1.amazonaws.com").setPort(11217).setRack("us-east-1e").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-80-65-203.compute-1.amazonaws.com").setPort(11218).setRack("us-east-1e").setStatus(Status.Up).createHost()); HttpEndpointBasedTokenMapSupplier tokenSupplier = new HttpEndpointBasedTokenMapSupplier("us-east-1d", 11211); @@ -145,14 +146,14 @@ public void testParseJsonWithHastags() throws Exception { List hostList = new ArrayList(); - hostList.add(new Host("ec2-54-237-143-4.compute-1.amazonaws.com", 11211, "us-east-1d", Status.Up)); - hostList.add(new Host("ec2-50-17-65-2.compute-1.amazonaws.com", 11211, "us-east-1d", Status.Up)); - hostList.add(new Host("ec2-54-83-87-174.compute-1.amazonaws.com", 11211, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-81-138-73.compute-1.amazonaws.com", 11211, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-82-176-215.compute-1.amazonaws.com", 11211, "us-east-1c", Status.Up)); - hostList.add(new Host("ec2-54-82-83-115.compute-1.amazonaws.com", 11211, "us-east-1e", Status.Up)); - hostList.add(new Host("ec2-54-211-220-55.compute-1.amazonaws.com", 11211, "us-east-1e", Status.Up)); - hostList.add(new Host("ec2-54-80-65-203.compute-1.amazonaws.com", 11211, "us-east-1e", Status.Up)); + hostList.add(new HostBuilder().setHostname("ec2-54-237-143-4.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1d").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-50-17-65-2.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1d").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-83-87-174.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-81-138-73.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-176-215.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1c").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-82-83-115.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1e").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-211-220-55.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1e").setStatus(Status.Up).createHost()); + hostList.add(new HostBuilder().setHostname("ec2-54-80-65-203.compute-1.amazonaws.com").setPort(11211).setRack("us-east-1e").setStatus(Status.Up).createHost()); HttpEndpointBasedTokenMapSupplier tokenSupplier = new HttpEndpointBasedTokenMapSupplier("us-east-1d", 11211); diff --git a/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/CustomTokenSupplierExample.java b/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/CustomTokenSupplierExample.java index c4cc1f93..10e8917b 100644 --- a/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/CustomTokenSupplierExample.java +++ b/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/CustomTokenSupplierExample.java @@ -16,6 +16,7 @@ package com.netflix.dyno.demo.redis; import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.OperationResult; import com.netflix.dyno.connectionpool.TokenMapSupplier; @@ -24,7 +25,6 @@ import com.netflix.dyno.jedis.DynoJedisClient; import org.apache.log4j.BasicConfigurator; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -49,7 +49,7 @@ public void init() throws Exception { final int port = 6379; - final Host localHost = new Host("localhost", port, "localrack", Host.Status.Up); + final Host localHost = new HostBuilder().setHostname("localhost").setPort(port).setRack("localrack").setStatus(Host.Status.Up).createHost(); final HostSupplier localHostSupplier = new HostSupplier() { diff --git a/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoDistributedCounterDemo.java b/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoDistributedCounterDemo.java index 12802de3..710e556c 100644 --- a/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoDistributedCounterDemo.java +++ b/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoDistributedCounterDemo.java @@ -249,7 +249,7 @@ public static void main(String[] args) throws IOException { } try { - demo.initWithRemoteClusterFromEurekaUrl(args[0], 8102); + demo.initWithRemoteClusterFromEurekaUrl(args[0], 8102, false); //demo.runMultiThreadedCounter(numCounters); //demo.runMultiThreadedPipelineCounter(numCounters); diff --git a/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoJedisDemo.java b/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoJedisDemo.java index 6439a395..d1ce128e 100644 --- a/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoJedisDemo.java +++ b/dyno-demo/src/main/java/com/netflix/dyno/demo/redis/DynoJedisDemo.java @@ -19,6 +19,7 @@ import com.netflix.dyno.connectionpool.CursorBasedResult; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.Host.Status; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.OperationResult; import com.netflix.dyno.connectionpool.TokenMapSupplier; @@ -29,6 +30,7 @@ import com.netflix.dyno.jedis.DynoJedisClient; import com.netflix.dyno.jedis.DynoJedisPipeline; import org.apache.commons.cli.*; +import com.netflix.dyno.recipes.lock.DynoLockClient; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; @@ -75,6 +77,7 @@ public class DynoJedisDemo { protected DynoJedisClient client; protected DynoJedisClient shadowClusterClient; + private DynoLockClient dynoLockClient; protected int numKeys; @@ -92,13 +95,13 @@ public DynoJedisDemo(String primaryCluster, String shadowCluster, String localRa this.localRack = localRack; } - public void initWithLocalHost() throws Exception { + public void initWithLocalHost(boolean initLock) throws Exception { final int port = 6379; final HostSupplier localHostSupplier = new HostSupplier() { - final Host hostSupplierHost = new Host("localhost", localRack, Status.Up); + final Host hostSupplierHost = new HostBuilder().setHostname("localhost").setRack(localRack).setDatastorePort(6379).setStatus(Status.Up).createHost(); @Override public List getHosts() { @@ -108,7 +111,7 @@ public List getHosts() { final TokenMapSupplier tokenSupplier = new TokenMapSupplier() { - final Host tokenHost = new Host("localhost", port, localRack, Status.Up); + final Host tokenHost = new HostBuilder().setHostname("localhost").setPort(port).setDatastorePort(6379).setRack(localRack).setStatus(Status.Up).createHost(); final HostToken localHostToken = new HostToken(100000L, tokenHost); @Override @@ -122,21 +125,27 @@ public HostToken getTokenForHost(Host host, Set activeHosts) { } }; - init(localHostSupplier, port, tokenSupplier); + if (initLock) + initDynoLockClient(localHostSupplier, tokenSupplier, "test", "test"); + else + init(localHostSupplier, port, tokenSupplier); } - private void initWithRemoteCluster(final List hosts, final int port) throws Exception { + private void initWithRemoteCluster(String clusterName, final List hosts, final int port, boolean lock) throws Exception { final HostSupplier clusterHostSupplier = () -> hosts; - init(clusterHostSupplier, port, null); + if (lock) + initDynoLockClient(clusterHostSupplier, null, "test", clusterName); + else + init(clusterHostSupplier, port, null); } - public void initWithRemoteClusterFromFile(final String filename, final int port) throws Exception { - initWithRemoteCluster(readHostsFromFile(filename, port), port); + public void initWithRemoteClusterFromFile(final String filename, final int port, boolean lock) throws Exception { + initWithRemoteCluster(null, readHostsFromFile(filename, port), port, lock); } - public void initWithRemoteClusterFromEurekaUrl(final String clusterName, final int port) throws Exception { - initWithRemoteCluster(getHostsFromDiscovery(clusterName), port); + public void initWithRemoteClusterFromEurekaUrl(final String clusterName, final int port, boolean lock) throws Exception { + initWithRemoteCluster(clusterName, getHostsFromDiscovery(clusterName), port, lock); } public void initDualClientWithRemoteClustersFromFile(final String primaryHostsFile, final String shadowHostsFile, @@ -187,8 +196,8 @@ public void initDualWriterDemo(HostSupplier primaryClusterHostSupplier, HostSupp this.shadowClusterClient = new DynoJedisClient.Builder() .withApplicationName("demo") .withDynomiteClusterName("dyno-dev") - .withHostSupplier(primaryClusterHostSupplier) - .withTokenMapSupplier(primaryTokenSupplier) + .withHostSupplier(shadowClusterHostSupplier) + .withTokenMapSupplier(shadowTokenSupplier) .withCPConfig(shadowCPConfig) .build(); @@ -207,6 +216,16 @@ public void init(HostSupplier hostSupplier, int port, TokenMapSupplier tokenSupp .build(); } + public void initDynoLockClient(HostSupplier hostSupplier, TokenMapSupplier tokenMapSupplier, String appName, + String clusterName) { + dynoLockClient = new DynoLockClient.Builder().withApplicationName(appName) + .withDynomiteClusterName(clusterName) + .withTimeoutUnit(TimeUnit.MILLISECONDS) + .withTimeout(10000) + .withHostSupplier(hostSupplier) + .withTokenMapSupplier(tokenMapSupplier).build(); + } + public void runSimpleTest() throws Exception { this.numKeys = 10; @@ -641,7 +660,7 @@ private List readHostsFromFile(String filename, int port) throws Exception if (parts.length != 2) { throw new RuntimeException("Bad data format in file:" + line); } - Host host = new Host(parts[0].trim(), port, parts[1].trim(), Status.Up); + Host host = new HostBuilder().setHostname(parts[0].trim()).setPort(port).setRack(parts[1].trim()).setStatus(Status.Up).createHost(); hosts.add(host); } } finally { @@ -919,7 +938,7 @@ private List getHostsFromDiscovery(final String clusterName) { for (Map map : handler.getList()) { String rack = map.get("availability-zone"); Status status = map.get("status").equalsIgnoreCase("UP") ? Status.Up : Status.Down; - Host host = new Host(map.get("public-hostname"), map.get("local-ipv4"), rack, status); + Host host = new HostBuilder().setHostname(map.get("public-hostname")).setIpAddress(map.get("local-ipv4")).setRack(rack).setStatus(status).createHost(); hosts.add(host); System.out.println("Host: " + host); } @@ -1090,6 +1109,8 @@ public void runEvalShaTest() throws Exception { * */ public static void main(String args[]) throws IOException { + Option lock = new Option("k", "lock", false, "Dyno Lock"); + lock.setArgName("lock"); Option primaryCluster = new Option("p", "primaryCluster", true, "Primary cluster"); primaryCluster.setArgName("clusterName"); @@ -1109,6 +1130,7 @@ public static void main(String args[]) throws IOException { Options options = new Options(); options.addOptionGroup(cluster) .addOption(secondaryCluster) + .addOption(lock) .addOption(test); Properties props = new Properties(); @@ -1133,16 +1155,20 @@ public static void main(String args[]) throws IOException { CommandLine cli = parser.parse(options, args); int testNumber = Integer.parseInt(cli.getOptionValue("t")); + boolean isLock = cli.hasOption("k"); if (cli.hasOption("l")) { demo = new DynoJedisDemo("dyno-localhost", rack); - demo.initWithLocalHost(); + demo.initWithLocalHost(isLock); + } else if (cli.hasOption("l")) { + demo = new DynoJedisDemo("dyno-localhost", rack); + demo.initWithLocalHost(false); } else { if (!cli.hasOption("s")) { demo = new DynoJedisDemo(cli.getOptionValue("p"), rack); if (hostsFile != null) { - demo.initWithRemoteClusterFromFile(hostsFile, port); + demo.initWithRemoteClusterFromFile(hostsFile, port, isLock); } else { - demo.initWithRemoteClusterFromEurekaUrl(cli.getOptionValue("p"), port); + demo.initWithRemoteClusterFromEurekaUrl(cli.getOptionValue("p"), port, isLock); } } else { demo = new DynoJedisDemo(cli.getOptionValue("p"), cli.getOptionValue("s"), rack); @@ -1205,6 +1231,10 @@ public static void main(String args[]) throws IOException { break; } + case 14: { + demo.runLockTest(); + break; + } } // demo.runSinglePipeline(); @@ -1236,4 +1266,15 @@ public static void main(String args[]) throws IOException { } } + private void runLockTest() throws InterruptedException { + String resourceName = "testResource"; + long ttl = 5_000; + boolean value = dynoLockClient.acquireLock(resourceName, ttl, (resource) -> logger.info("Extension failed")); + if (value) { + logger.info("Acquired lock on resource {} for {} ms ", resourceName, value); + } + dynoLockClient.logLocks(); + Thread.sleep(100_000); + dynoLockClient.releaseLock(resourceName); + } } \ No newline at end of file diff --git a/dyno-demo/src/main/resources/demo.properties b/dyno-demo/src/main/resources/demo.properties index b94154d8..b0679dad 100644 --- a/dyno-demo/src/main/resources/demo.properties +++ b/dyno-demo/src/main/resources/demo.properties @@ -3,16 +3,18 @@ # # Uncomment properties as necessary. -#LOCAL_DATACENTER=us-east-1 -#LOCAL_RACK=us-east-1d +LOCAL_DATACENTER=us-east-1 +LOCAL_RACK=us-east-1d NETFLIX_STACK=dyno_demo -#EC2_AVAILABILITY_ZONE=us-east-1c +EC2_AVAILABILITY_ZONE=us-east-1c netflix.appinfo.name=dyno_demo netflix.environment=test netflix.appinfo.metadata.enableRoute53=false netflix.discovery.registration.enabled=false netflix.appinfo.validateInstanceId=false dyno.demo.retryPolicy=RetryNTimes:2 +dyno.demo.dualwrite.enabled=true +dyno.demo.dualwrite.percentage=100 dyno.demo.port=8102 dyno.demo.discovery.prod=discoveryreadonly.%s.dynprod.netflix.net:7001/v2/apps dyno.demo.discovery.test=discoveryreadonly.%s.dyntest.netflix.net:7001/v2/apps diff --git a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisClient.java b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisClient.java index 0fa169fc..e7cebcd0 100644 --- a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisClient.java +++ b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisClient.java @@ -34,17 +34,16 @@ import com.netflix.dyno.connectionpool.TopologyView; import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; -import com.netflix.dyno.connectionpool.exception.NoAvailableHostsException; import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl; import com.netflix.dyno.connectionpool.impl.ConnectionPoolImpl; -import com.netflix.dyno.connectionpool.impl.lb.HostToken; -import com.netflix.dyno.connectionpool.impl.lb.HttpEndpointBasedTokenMapSupplier; import com.netflix.dyno.connectionpool.impl.utils.CollectionUtils; import com.netflix.dyno.connectionpool.impl.utils.ZipUtils; import com.netflix.dyno.contrib.ArchaiusConnectionPoolConfiguration; import com.netflix.dyno.contrib.DynoCPMonitor; import com.netflix.dyno.contrib.DynoOPMonitor; import com.netflix.dyno.contrib.EurekaHostsSupplier; +import com.netflix.dyno.jedis.operation.BaseKeyOperation; +import com.netflix.dyno.jedis.operation.MultiKeyOperation; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; @@ -53,7 +52,6 @@ import redis.clients.jedis.params.geo.GeoRadiusParam; import redis.clients.jedis.params.sortedset.ZAddParams; import redis.clients.jedis.params.sortedset.ZIncrByParams; - import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.util.ArrayList; @@ -107,90 +105,6 @@ public String getClusterName() { return clusterName; } - private abstract class BaseKeyOperation implements Operation { - - private final String key; - private final byte[] binaryKey; - private final OpName op; - - private BaseKeyOperation(final String k, final OpName o) { - this.key = k; - this.binaryKey = null; - this.op = o; - } - - private BaseKeyOperation(final byte[] k, final OpName o) { - this.key = null; - this.binaryKey = k; - this.op = o; - } - - @Override - public String getName() { - return op.name(); - } - - @Override - public String getStringKey() { - return this.key; - } - - public byte[] getBinaryKey() { - return this.binaryKey; - } - - } - - /** - * A poor man's solution for multikey operation. This is similar to - * basekeyoperation just that it takes a list of keys as arguments. For - * token aware, we just use the first key in the list. Ideally we should be - * doing a scatter gather - */ - private abstract class MultiKeyOperation implements Operation { - - private final List keys; - private final List binaryKeys; - private final OpName op; - - private MultiKeyOperation(final List keys, final OpName o) { - Object firstKey = (keys != null && keys.size() > 0) ? keys.get(0) : null; - - if (firstKey != null) { - if (firstKey instanceof String) {//string key - this.keys = keys; - this.binaryKeys = null; - } else if (firstKey instanceof byte[]) {//binary key - this.keys = null; - this.binaryKeys = keys; - } else {//something went wrong here - this.keys = null; - this.binaryKeys = null; - } - } else { - this.keys = null; - this.binaryKeys = null; - } - - this.op = o; - } - - @Override - public String getName() { - return op.name(); - } - - @Override - public String getStringKey() { - return (this.keys != null) ? this.keys.get(0) : null; - } - - public byte[] getBinaryKey() { - return (binaryKeys != null) ? binaryKeys.get(0) : null; - } - - } - /** * The following commands are supported * @@ -4177,6 +4091,7 @@ public static class Builder { private SSLSocketFactory sslSocketFactory; private TokenMapSupplier tokenMapSupplier; private TokenMapSupplier dualWriteTokenMapSupplier; + private boolean isDatastoreClient; public Builder() { } @@ -4247,6 +4162,11 @@ public Builder withSSLSocketFactory(SSLSocketFactory sslSocketFactory) { return this; } + public Builder isDatastoreClient(boolean isDatastoreClient) { + this.isDatastoreClient = isDatastoreClient; + return this; + } + public DynoJedisClient build() { assert (appName != null); assert (clusterName != null); @@ -4255,6 +4175,7 @@ public DynoJedisClient build() { cpConfig = new ArchaiusConnectionPoolConfiguration(appName); Logger.info("Dyno Client runtime properties: " + cpConfig.toString()); } + cpConfig.setConnectToDatastore(isDatastoreClient); if (cpConfig.isDualWriteEnabled()) { return buildDynoDualWriterClient(); @@ -4297,9 +4218,9 @@ private DynoDualWriterClient buildDynoDualWriterClient() { DynoCPMonitor shadowCPMonitor = new DynoCPMonitor(shadowAppName); DynoOPMonitor shadowOPMonitor = new DynoOPMonitor(shadowAppName); - JedisConnectionFactory connFactory = new JedisConnectionFactory(shadowOPMonitor, sslSocketFactory); - final ConnectionPoolImpl shadowPool = startConnectionPool(shadowAppName, connFactory, shadowConfig, - shadowCPMonitor); + DynoJedisUtils.updateConnectionPoolConfig(shadowConfig, shadowSupplier, dualWriteTokenMapSupplier, discoveryClient, clusterName); + final ConnectionPool shadowPool = DynoJedisUtils.createConnectionPool(shadowAppName, shadowOPMonitor, shadowCPMonitor, shadowConfig, + sslSocketFactory); // Construct a connection pool with the shadow cluster settings DynoJedisClient shadowClient = new DynoJedisClient(shadowAppName, dualWriteClusterName, shadowPool, @@ -4309,7 +4230,10 @@ private DynoDualWriterClient buildDynoDualWriterClient() { DynoOPMonitor opMonitor = new DynoOPMonitor(appName); ConnectionPoolMonitor cpMonitor = (this.cpMonitor == null) ? new DynoCPMonitor(appName) : this.cpMonitor; - final ConnectionPoolImpl pool = createConnectionPool(appName, opMonitor, cpMonitor); + DynoJedisUtils.updateConnectionPoolConfig(cpConfig, hostSupplier, tokenMapSupplier, discoveryClient, + clusterName); + final ConnectionPool pool = DynoJedisUtils.createConnectionPool(appName, opMonitor, cpMonitor, + cpConfig, sslSocketFactory); if (dualWriteDial != null) { if (shadowConfig.getDualWritePercentage() > 0) { @@ -4327,145 +4251,14 @@ private DynoJedisClient buildDynoJedisClient() { DynoOPMonitor opMonitor = new DynoOPMonitor(appName); ConnectionPoolMonitor cpMonitor = (this.cpMonitor == null) ? new DynoCPMonitor(appName) : this.cpMonitor; - final ConnectionPoolImpl pool = createConnectionPool(appName, opMonitor, cpMonitor); + DynoJedisUtils.updateConnectionPoolConfig(cpConfig, hostSupplier, tokenMapSupplier, discoveryClient, + clusterName); + final ConnectionPool pool = DynoJedisUtils.createConnectionPool(appName, opMonitor, cpMonitor, + cpConfig, sslSocketFactory); return new DynoJedisClient(appName, clusterName, pool, opMonitor, cpMonitor); } - private ConnectionPoolImpl createConnectionPool(String appName, DynoOPMonitor opMonitor, - ConnectionPoolMonitor cpMonitor) { - - if (hostSupplier == null) { - if (discoveryClient == null) { - throw new DynoConnectException("HostSupplier not provided. Cannot initialize EurekaHostsSupplier " - + "which requires a DiscoveryClient"); - } else { - hostSupplier = new EurekaHostsSupplier(clusterName, discoveryClient); - } - } - - cpConfig.withHostSupplier(hostSupplier); - if (tokenMapSupplier != null) - cpConfig.withTokenSupplier(tokenMapSupplier); - setLoadBalancingStrategy(cpConfig); - setHashtagConnectionPool(hostSupplier, cpConfig); - JedisConnectionFactory connFactory = new JedisConnectionFactory(opMonitor, sslSocketFactory); - - return startConnectionPool(appName, connFactory, cpConfig, cpMonitor); - } - - private ConnectionPoolImpl startConnectionPool(String appName, JedisConnectionFactory connFactory, - ConnectionPoolConfigurationImpl cpConfig, ConnectionPoolMonitor cpMonitor) { - - final ConnectionPoolImpl pool = new ConnectionPoolImpl(connFactory, cpConfig, cpMonitor); - - try { - Logger.info("Starting connection pool for app " + appName); - - pool.start().get(); - - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - pool.shutdown(); - } - })); - } catch (NoAvailableHostsException e) { - if (cpConfig.getFailOnStartupIfNoHosts()) { - throw new RuntimeException(e); - } - - Logger.warn("UNABLE TO START CONNECTION POOL -- IDLING"); - - pool.idle(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return pool; - } - - private void setLoadBalancingStrategy(ConnectionPoolConfigurationImpl config) { - if (ConnectionPoolConfiguration.LoadBalancingStrategy.TokenAware == config.getLoadBalancingStrategy()) { - if (config.getTokenSupplier() == null) { - Logger.warn( - "TOKEN AWARE selected and no token supplier found, using default HttpEndpointBasedTokenMapSupplier()"); - config.withTokenSupplier(new HttpEndpointBasedTokenMapSupplier()); - } - - if (config.getLocalRack() == null && config.localZoneAffinity()) { - String warningMessage = "DynoJedisClient for app=[" + config.getName() - + "] is configured for local rack affinity " - + "but cannot determine the local rack! DISABLING rack affinity for this instance. " - + "To make the client aware of the local rack either use " - + "ConnectionPoolConfigurationImpl.setLocalRack() when constructing the client " - + "instance or ensure EC2_AVAILABILTY_ZONE is set as an environment variable, e.g. " - + "run with -DLOCAL_RACK=us-east-1c"; - config.setLocalZoneAffinity(false); - Logger.warn(warningMessage); - } - } - } - - /** - * Set the hash to the connection pool if is provided by Dynomite - * - * @param hostSupplier - * @param config - */ - private void setHashtagConnectionPool(HostSupplier hostSupplier, ConnectionPoolConfigurationImpl config) { - // Find the hosts from host supplier - List hosts = (List) hostSupplier.getHosts(); - Collections.sort(hosts); - // Convert the arraylist to set - - // Take the token map supplier (aka the token topology from - // Dynomite) - TokenMapSupplier tokenMapSupplier = config.getTokenSupplier(); - - // Create a list of host/Tokens - List hostTokens; - if (tokenMapSupplier != null) { - Set hostSet = new HashSet(hosts); - hostTokens = tokenMapSupplier.getTokens(hostSet); - /* Dyno cannot reach the TokenMapSupplier endpoint, - * therefore no nodes can be retrieved. - */ - if (hostTokens.isEmpty()) { - throw new DynoConnectException("No hosts in the TokenMapSupplier"); - } - } else { - throw new DynoConnectException("TokenMapSupplier not provided"); - } - - String hashtag = hostTokens.get(0).getHost().getHashtag(); - short numHosts = 0; - // Update inner state with the host tokens. - for (HostToken hToken : hostTokens) { - /** - * Checking hashtag consistency from all Dynomite hosts. If - * hashtags are not consistent, we need to throw an exception. - */ - String hashtagNew = hToken.getHost().getHashtag(); - - if (hashtag != null && !hashtag.equals(hashtagNew)) { - Logger.error("Hashtag mismatch across hosts"); - throw new RuntimeException("Hashtags are different across hosts"); - } // addressing case hashtag = null, hashtag = {} ... - else if (numHosts > 0 && hashtag == null && hashtagNew != null) { - Logger.error("Hashtag mismatch across hosts"); - throw new RuntimeException("Hashtags are different across hosts"); - - } - hashtag = hashtagNew; - numHosts++; - } - - if (hashtag != null) { - config.withHashtag(hashtag); - } - } - } /** diff --git a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisUtils.java b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisUtils.java new file mode 100644 index 00000000..da04f21a --- /dev/null +++ b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/DynoJedisUtils.java @@ -0,0 +1,164 @@ +package com.netflix.dyno.jedis; + +import com.netflix.discovery.EurekaClient; +import com.netflix.dyno.connectionpool.ConnectionFactory; +import com.netflix.dyno.connectionpool.ConnectionPool; +import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration; +import com.netflix.dyno.connectionpool.ConnectionPoolMonitor; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostSupplier; +import com.netflix.dyno.connectionpool.TokenMapSupplier; +import com.netflix.dyno.connectionpool.exception.DynoConnectException; +import com.netflix.dyno.connectionpool.exception.NoAvailableHostsException; +import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl; +import com.netflix.dyno.connectionpool.impl.ConnectionPoolImpl; +import com.netflix.dyno.connectionpool.impl.lb.HostToken; +import com.netflix.dyno.connectionpool.impl.lb.HttpEndpointBasedTokenMapSupplier; +import com.netflix.dyno.contrib.DynoOPMonitor; +import com.netflix.dyno.contrib.EurekaHostsSupplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; + +import javax.inject.Singleton; +import javax.net.ssl.SSLSocketFactory; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +@Singleton +public class DynoJedisUtils { + + + private static final Logger logger = LoggerFactory.getLogger(DynoJedisClient.class); + + public static void updateConnectionPoolConfig(ConnectionPoolConfigurationImpl cpConfig, + HostSupplier hostSupplier, TokenMapSupplier tokenMapSupplier, + EurekaClient discoveryClient, String clusterName) { + if (hostSupplier == null) { + if (discoveryClient == null) { + throw new DynoConnectException("HostSupplier not provided. Cannot initialize EurekaHostsSupplier " + + "which requires a DiscoveryClient"); + } else { + hostSupplier = new EurekaHostsSupplier(clusterName, discoveryClient); + } + } + cpConfig.withHostSupplier(hostSupplier); + if (tokenMapSupplier != null) + cpConfig.withTokenSupplier(tokenMapSupplier); + setLoadBalancingStrategy(cpConfig); + setHashtagConnectionPool(hostSupplier, cpConfig); + } + + public static ConnectionPool createConnectionPool(String appName, DynoOPMonitor opMonitor, + ConnectionPoolMonitor cpMonitor, ConnectionPoolConfiguration cpConfig, + SSLSocketFactory sslSocketFactory) { + JedisConnectionFactory connFactory = new JedisConnectionFactory(opMonitor, sslSocketFactory); + + return startConnectionPool(appName, connFactory, cpConfig, cpMonitor); + } + + private static ConnectionPool startConnectionPool(String appName, ConnectionFactory connFactory, + ConnectionPoolConfiguration cpConfig, ConnectionPoolMonitor cpMonitor) { + + final ConnectionPool pool = new ConnectionPoolImpl<>(connFactory, cpConfig, cpMonitor); + + try { + logger.info("Starting connection pool for app " + appName); + + pool.start().get(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> pool.shutdown())); + } catch (NoAvailableHostsException e) { + if (cpConfig.getFailOnStartupIfNoHosts()) { + throw new RuntimeException(e); + } + + logger.warn("UNABLE TO START CONNECTION POOL -- IDLING"); + + pool.idle(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return pool; + } + + private static void setLoadBalancingStrategy(ConnectionPoolConfigurationImpl config) { + if (ConnectionPoolConfiguration.LoadBalancingStrategy.TokenAware == config.getLoadBalancingStrategy()) { + if (config.getTokenSupplier() == null) { + logger.warn( + "TOKEN AWARE selected and no token supplier found, using default HttpEndpointBasedTokenMapSupplier()"); + config.withTokenSupplier(new HttpEndpointBasedTokenMapSupplier()); + } + + if (config.getLocalRack() == null && config.localZoneAffinity()) { + String warningMessage = "DynoJedisClient for app=[" + config.getName() + + "] is configured for local rack affinity " + + "but cannot determine the local rack! DISABLING rack affinity for this instance. " + + "To make the client aware of the local rack either use " + + "ConnectionPoolConfigurationImpl.setLocalRack() when constructing the client " + + "instance or ensure EC2_AVAILABILTY_ZONE is set as an environment variable, e.g. " + + "run with -DLOCAL_RACK=us-east-1c"; + config.setLocalZoneAffinity(false); + logger.warn(warningMessage); + } + } + } + + /** + * Set the hash to the connection pool if is provided by Dynomite + * + * @param hostSupplier + * @param config + */ + private static void setHashtagConnectionPool(HostSupplier hostSupplier, ConnectionPoolConfigurationImpl config) { + // Find the hosts from host supplier + List hosts = (List) hostSupplier.getHosts(); + Collections.sort(hosts); + + // Take the token map supplier (aka the token topology from + // Dynomite) + TokenMapSupplier tokenMapSupplier = config.getTokenSupplier(); + + // Create a list of host/Tokens + List hostTokens; + if (tokenMapSupplier != null) { + Set hostSet = new HashSet(hosts); + hostTokens = tokenMapSupplier.getTokens(hostSet); + /* Dyno cannot reach the TokenMapSupplier endpoint, + * therefore no nodes can be retrieved. + */ + if (hostTokens.isEmpty()) { + throw new DynoConnectException("No hosts in the TokenMapSupplier"); + } + } else { + throw new DynoConnectException("TokenMapSupplier not provided"); + } + + String hashtag = hostTokens.get(0).getHost().getHashtag(); + Stream htStream = hostTokens.stream().map(hostToken -> hostToken.getHost().getHashtag()); + + if (hashtag == null) { + htStream.filter(ht -> ht != null).findAny().ifPresent(ignore -> { + logger.error("Hashtag mismatch across hosts"); + throw new RuntimeException("Hashtags are different across hosts"); + }); + } else { + /** + * Checking hashtag consistency from all Dynomite hosts. If + * hashtags are not consistent, we need to throw an exception. + */ + htStream.filter(ht -> !hashtag.equals(ht)).findAny().ifPresent(ignore -> { + logger.error("Hashtag mismatch across hosts"); + throw new RuntimeException("Hashtags are different across hosts"); + }); + } + + if (hashtag != null) { + config.withHashtag(hashtag); + } + } +} diff --git a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/JedisConnectionFactory.java b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/JedisConnectionFactory.java index 7327c9a1..cc406bb0 100644 --- a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/JedisConnectionFactory.java +++ b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/JedisConnectionFactory.java @@ -15,16 +15,10 @@ ******************************************************************************/ package com.netflix.dyno.jedis; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.lang.NotImplementedException; -import org.slf4j.LoggerFactory; - import com.netflix.dyno.connectionpool.AsyncOperation; import com.netflix.dyno.connectionpool.Connection; import com.netflix.dyno.connectionpool.ConnectionContext; import com.netflix.dyno.connectionpool.ConnectionFactory; -import com.netflix.dyno.connectionpool.ConnectionObservor; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.ListenableFuture; @@ -34,10 +28,10 @@ import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; import com.netflix.dyno.connectionpool.exception.FatalConnectionException; -import com.netflix.dyno.connectionpool.exception.ThrottledException; import com.netflix.dyno.connectionpool.impl.ConnectionContextImpl; import com.netflix.dyno.connectionpool.impl.OperationResultImpl; - +import org.apache.commons.lang.NotImplementedException; +import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.exceptions.JedisConnectionException; @@ -45,6 +39,7 @@ import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocketFactory; +import java.util.concurrent.TimeUnit; public class JedisConnectionFactory implements ConnectionFactory { @@ -59,12 +54,18 @@ public JedisConnectionFactory(OperationMonitor monitor, SSLSocketFactory sslSock } @Override - public Connection createConnection(HostConnectionPool pool, ConnectionObservor connectionObservor) - throws DynoConnectException, ThrottledException { - + public Connection createConnection(HostConnectionPool pool) + throws DynoConnectException { return new JedisConnection(pool); } + @Override + public Connection createConnectionWithDataStore(HostConnectionPool pool) + throws DynoConnectException { + return new JedisConnection(pool, true); + } + + // TODO: raghu compose redisconnection with jedisconnection in it public class JedisConnection implements Connection { private final HostConnectionPool hostPool; @@ -74,17 +75,23 @@ public class JedisConnection implements Connection { private DynoConnectException lastDynoException; public JedisConnection(HostConnectionPool hostPool) { + this(hostPool, false); + } + + public JedisConnection(HostConnectionPool hostPool, boolean connectDataStore) { this.hostPool = hostPool; Host host = hostPool.getHost(); + int port = connectDataStore ? host.getDatastorePort() : host.getPort(); + if (sslSocketFactory == null) { - JedisShardInfo shardInfo = new JedisShardInfo(host.getHostAddress(), host.getPort(), + JedisShardInfo shardInfo = new JedisShardInfo(host.getHostAddress(), port, hostPool.getConnectionTimeout(), hostPool.getSocketTimeout(), Sharded.DEFAULT_WEIGHT); shardInfo.setPassword(host.getPassword()); jedisClient = new Jedis(shardInfo); } else { - JedisShardInfo shardInfo = new JedisShardInfo(host.getHostAddress(), host.getPort(), + JedisShardInfo shardInfo = new JedisShardInfo(host.getHostAddress(), port, hostPool.getConnectionTimeout(), hostPool.getSocketTimeout(), Sharded.DEFAULT_WEIGHT, true, sslSocketFactory, new SSLParameters(), null); shardInfo.setPassword(host.getPassword()); diff --git a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/BaseKeyOperation.java b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/BaseKeyOperation.java new file mode 100644 index 00000000..ce668f87 --- /dev/null +++ b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/BaseKeyOperation.java @@ -0,0 +1,39 @@ +package com.netflix.dyno.jedis.operation; + +import com.netflix.dyno.connectionpool.Operation; +import com.netflix.dyno.jedis.OpName; +import redis.clients.jedis.Jedis; + +public abstract class BaseKeyOperation implements Operation { + + private final String key; + private final byte[] binaryKey; + private final OpName op; + + public BaseKeyOperation(final String k, final OpName o) { + this.key = k; + this.binaryKey = null; + this.op = o; + } + + public BaseKeyOperation(final byte[] k, final OpName o) { + this.key = null; + this.binaryKey = k; + this.op = o; + } + + @Override + public String getName() { + return op.name(); + } + + @Override + public String getStringKey() { + return this.key; + } + + public byte[] getBinaryKey() { + return this.binaryKey; + } + +} diff --git a/dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/MultiKeyOperation.java b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/MultiKeyOperation.java new file mode 100644 index 00000000..9a62fc57 --- /dev/null +++ b/dyno-jedis/src/main/java/com/netflix/dyno/jedis/operation/MultiKeyOperation.java @@ -0,0 +1,61 @@ +package com.netflix.dyno.jedis.operation; + +import com.netflix.dyno.connectionpool.Operation; +import com.netflix.dyno.jedis.OpName; +import redis.clients.jedis.Jedis; + +import java.util.List; + +/** + * A poor man's solution for multikey operation. This is similar to + * basekeyoperation just that it takes a list of keys as arguments. For + * token aware, we just use the first key in the list. Ideally we should be + * doing a scatter gather + */ +public abstract class MultiKeyOperation implements Operation { + + private final List keys; + private final List binaryKeys; + private final OpName op; + + public MultiKeyOperation(final List keys, final OpName o) { + Object firstKey = (keys != null && keys.size() > 0) ? keys.get(0) : null; + + if (firstKey != null) { + if (firstKey instanceof String) {//string key + this.keys = keys; + this.binaryKeys = null; + } else if (firstKey instanceof byte[]) {//binary key + this.keys = null; + this.binaryKeys = keys; + } else {//something went wrong here + this.keys = null; + this.binaryKeys = null; + } + } else { + this.keys = null; + this.binaryKeys = null; + } + + this.op = o; + } + + @Override + public String getName() { + return op.name(); + } + + /** + * Sends back only the first key of the multi key operation. + * @return + */ + @Override + public String getStringKey() { + return (this.keys != null) ? this.keys.get(0) : null; + } + + public byte[] getBinaryKey() { + return (binaryKeys != null) ? binaryKeys.get(0) : null; + } + +} diff --git a/dyno-jedis/src/test/java/com/netflix/dyno/jedis/JedisConnectionFactoryIntegrationTest.java b/dyno-jedis/src/test/java/com/netflix/dyno/jedis/JedisConnectionFactoryIntegrationTest.java index 74679367..43a36033 100644 --- a/dyno-jedis/src/test/java/com/netflix/dyno/jedis/JedisConnectionFactoryIntegrationTest.java +++ b/dyno-jedis/src/test/java/com/netflix/dyno/jedis/JedisConnectionFactoryIntegrationTest.java @@ -16,6 +16,7 @@ package com.netflix.dyno.jedis; import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.TokenMapSupplier; import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl; @@ -41,7 +42,7 @@ public class JedisConnectionFactoryIntegrationTest { private final String datacenter = "rack"; - private final Host localHost = new Host("localhost", port, rack, Host.Status.Up); + private final Host localHost = new HostBuilder().setHostname("localhost").setPort(port).setRack(rack).setStatus(Host.Status.Up).createHost(); private final HostSupplier localHostSupplier = new HostSupplier() { diff --git a/dyno-jedis/src/test/java/com/netflix/dyno/jedis/RedisAuthenticationIntegrationTest.java b/dyno-jedis/src/test/java/com/netflix/dyno/jedis/RedisAuthenticationIntegrationTest.java index fbee1b23..42b4b25f 100644 --- a/dyno-jedis/src/test/java/com/netflix/dyno/jedis/RedisAuthenticationIntegrationTest.java +++ b/dyno-jedis/src/test/java/com/netflix/dyno/jedis/RedisAuthenticationIntegrationTest.java @@ -5,6 +5,7 @@ import com.netflix.dyno.connectionpool.ConnectionPoolConfiguration; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.Host.Status; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.TokenMapSupplier; @@ -14,12 +15,6 @@ import com.netflix.dyno.connectionpool.impl.HostConnectionPoolImpl; import com.netflix.dyno.connectionpool.impl.lb.HostToken; import com.netflix.dyno.contrib.DynoOPMonitor; - -import java.net.ConnectException; -import java.util.Collections; -import java.util.List; -import java.util.Set; - import org.junit.After; import org.junit.Assert; import org.junit.Assume; @@ -30,6 +25,11 @@ import redis.embedded.RedisServer; import redis.embedded.RedisServerBuilder; +import java.net.ConnectException; +import java.util.Collections; +import java.util.List; +import java.util.Set; + public class RedisAuthenticationIntegrationTest { private static final int REDIS_PORT = 8998; @@ -56,7 +56,7 @@ public void testDynoClient_noAuthSuccess() throws Exception { redisServer = new RedisServer(REDIS_PORT); redisServer.start(); - Host noAuthHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Host.Status.Up); + Host noAuthHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).createHost(); TokenMapSupplierImpl tokenMapSupplier = new TokenMapSupplierImpl(noAuthHost); DynoJedisClient dynoClient = constructJedisClient(tokenMapSupplier, () -> Collections.singletonList(noAuthHost)); @@ -76,7 +76,7 @@ public void testDynoClient_authSuccess() throws Exception { .build(); redisServer.start(); - Host authHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Status.Up, null, "password"); + Host authHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).setHashtag(null).setPassword("password").createHost(); TokenMapSupplierImpl tokenMapSupplier = new TokenMapSupplierImpl(authHost); DynoJedisClient dynoClient = constructJedisClient(tokenMapSupplier, @@ -94,7 +94,7 @@ public void testJedisConnFactory_noAuthSuccess() throws Exception { redisServer = new RedisServer(REDIS_PORT); redisServer.start(); - Host noAuthHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Host.Status.Up); + Host noAuthHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).createHost(); JedisConnectionFactory conFactory = new JedisConnectionFactory(new DynoOPMonitor("some-application-name"), null); @@ -103,7 +103,7 @@ public void testJedisConnFactory_noAuthSuccess() throws Exception { HostConnectionPool hostConnectionPool = new HostConnectionPoolImpl<>(noAuthHost, conFactory, cpConfig, poolMonitor); Connection connection = conFactory - .createConnection(hostConnectionPool, null); + .createConnection(hostConnectionPool); connection.execPing(); } @@ -116,7 +116,7 @@ public void testJedisConnFactory_authSuccess() throws Exception { .build(); redisServer.start(); - Host authHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Status.Up, null, "password"); + Host authHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).setHashtag(null).setPassword("password").createHost(); JedisConnectionFactory conFactory = new JedisConnectionFactory(new DynoOPMonitor("some-application-name"), null); @@ -125,14 +125,14 @@ public void testJedisConnFactory_authSuccess() throws Exception { HostConnectionPool hostConnectionPool = new HostConnectionPoolImpl<>(authHost, conFactory, cpConfig, poolMonitor); Connection connection = conFactory - .createConnection(hostConnectionPool, null); + .createConnection(hostConnectionPool); connection.execPing(); } @Test public void testJedisConnFactory_connectionFailed() throws Exception { - Host noAuthHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Host.Status.Up); + Host noAuthHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).createHost(); JedisConnectionFactory conFactory = new JedisConnectionFactory(new DynoOPMonitor("some-application-name"), null); @@ -141,7 +141,7 @@ public void testJedisConnFactory_connectionFailed() throws Exception { HostConnectionPool hostConnectionPool = new HostConnectionPoolImpl<>(noAuthHost, conFactory, cpConfig, poolMonitor); Connection connection = conFactory - .createConnection(hostConnectionPool, null); + .createConnection(hostConnectionPool); try { connection.execPing(); @@ -160,7 +160,7 @@ public void testJedisConnFactory_authenticationRequired() throws Exception { .build(); redisServer.start(); - Host noAuthHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Host.Status.Up); + Host noAuthHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).createHost(); JedisConnectionFactory conFactory = new JedisConnectionFactory(new DynoOPMonitor("some-application-name"), null); @@ -169,7 +169,7 @@ public void testJedisConnFactory_authenticationRequired() throws Exception { HostConnectionPool hostConnectionPool = new HostConnectionPoolImpl<>(noAuthHost, conFactory, cpConfig, poolMonitor); Connection connection = conFactory - .createConnection(hostConnectionPool, null); + .createConnection(hostConnectionPool); try { connection.execPing(); @@ -187,8 +187,7 @@ public void testJedisConnFactory_invalidPassword() throws Exception { .build(); redisServer.start(); - Host authHost = new Host("localhost", REDIS_PORT, REDIS_RACK, Status.Up, null, - "invalid-password"); + Host authHost = new HostBuilder().setHostname("localhost").setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Status.Up).setHashtag(null).setPassword("invalid-password").createHost(); JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(new DynoOPMonitor("some-application-name"), null); @@ -198,7 +197,7 @@ public void testJedisConnFactory_invalidPassword() throws Exception { HostConnectionPool hostConnectionPool = new HostConnectionPoolImpl<>(authHost, jedisConnectionFactory, connectionPoolConfiguration, new CountingConnectionPoolMonitor()); Connection connection = jedisConnectionFactory - .createConnection(hostConnectionPool, null); + .createConnection(hostConnectionPool); try { connection.execPing(); diff --git a/dyno-jedis/src/test/java/com/netflix/dyno/jedis/UnitTestTokenMapAndHostSupplierImpl.java b/dyno-jedis/src/test/java/com/netflix/dyno/jedis/UnitTestTokenMapAndHostSupplierImpl.java index b4d10b7e..08062290 100644 --- a/dyno-jedis/src/test/java/com/netflix/dyno/jedis/UnitTestTokenMapAndHostSupplierImpl.java +++ b/dyno-jedis/src/test/java/com/netflix/dyno/jedis/UnitTestTokenMapAndHostSupplierImpl.java @@ -1,6 +1,7 @@ package com.netflix.dyno.jedis; import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.TokenMapSupplier; import com.netflix.dyno.connectionpool.impl.lb.HostToken; @@ -24,7 +25,7 @@ public UnitTestTokenMapAndHostSupplierImpl(int serverCount, String rack) throws redisServer.start(); redisServers.add(Pair.of(redisServer, port)); - Host host = new Host("localhost", port, rack, Host.Status.Up); + Host host = new HostBuilder().setHostname("localhost").setPort(port).setRack(rack).setStatus(Host.Status.Up).createHost(); hostTokenMap.put(host, new HostToken((long) i * hostTokenStride, host)); } } diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/DynoLockClient.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/DynoLockClient.java new file mode 100644 index 00000000..4db3605f --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/DynoLockClient.java @@ -0,0 +1,428 @@ +/******************************************************************************* + * Copyright 2018 Netflix + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package com.netflix.dyno.recipes.lock; + +import com.netflix.discovery.EurekaClient; +import com.netflix.dyno.connectionpool.ConnectionPool; +import com.netflix.dyno.connectionpool.ConnectionPoolMonitor; +import com.netflix.dyno.connectionpool.HostSupplier; +import com.netflix.dyno.connectionpool.TokenMapSupplier; +import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl; +import com.netflix.dyno.contrib.ArchaiusConnectionPoolConfiguration; +import com.netflix.dyno.contrib.DynoCPMonitor; +import com.netflix.dyno.contrib.DynoOPMonitor; +import com.netflix.dyno.jedis.DynoJedisClient; +import com.netflix.dyno.jedis.DynoJedisUtils; +import com.netflix.dyno.recipes.lock.command.CheckAndRunHost; +import com.netflix.dyno.recipes.lock.command.ExtendHost; +import com.netflix.dyno.recipes.lock.command.LockHost; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Client for acquiring locks similar to the redlock implementation https://redis.io/topics/distlock + * + * This locking mechanism does not give the guarantees for safety but can be used for efficiency. + * + * We assume some amount of clock skew between the client and server instances. Any major deviations in this + * will result in reduced accuracy of the lock. + * + * In the common locking case we rely on TTL's being set on a majority of redis servers in order to achieve the right + * locking characteristic. If we are unable to reach a majority of hosts when we try to acquire a lock or extend a lock. + * + * We try to release locks on all the hosts when we either shutdown or are unable to lock on a majority of hosts successfully + * + * These are the main edge cases where locking might not be mutually exclusive + * + * 1. An instance we acquired the lock on goes down and gets replaced by a new instance before the lock TTL expires. + * As suggested in the blog above, we need to ensure that new servers take longer than TTL time to come up so that + * any existing locks would've expired by then.(the client side code cannot control how quickly your servers come up). This can + * become a real issue if you're bringing up new servers in containers which can come up in a few seconds and you are holding locks for longer + * than the amount of time it takes for a new server to come up. + * 2. You have the JVM go into GC from when you acquired the lock to when you are going to modify the resource blocked by the lock. + * The client needs to ensure that GC is not happening for a long enough time that it can effect the assumption of lock being held (or have an alert on long GCs). + * + */ +public class DynoLockClient { + + private static final Logger logger = LoggerFactory.getLogger(DynoJedisClient.class); + + private final ConnectionPool pool; + private final VotingHostsSelector votingHostsSelector; + private final ExecutorService service; + private final int quorum; + // We assume a small amount of clock drift. + private final double CLOCK_DRIFT = 0.01; + private TimeUnit timeoutUnit; + private long timeout; + private final ConcurrentHashMap resourceKeyMap = new ConcurrentHashMap<>(); + + public DynoLockClient(ConnectionPool pool, VotingHostsSelector votingHostsSelector, long timeout, TimeUnit unit) { + this.pool = pool; + this.votingHostsSelector = votingHostsSelector; + // Threads for locking and unlocking + this.service = Executors.newCachedThreadPool(); + this.quorum = votingHostsSelector.getVotingSize() / 2 + 1; + this.timeout = timeout; + this.timeoutUnit = unit; + // We want to release all locks in case of a graceful shutdown + Runtime.getRuntime().addShutdownHook(new Thread(() -> cleanup())); + } + + public void setTimeoutUnit(TimeUnit timeoutUnit) { + this.timeoutUnit = timeoutUnit; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + private static String getRandomString() { + return UUID.randomUUID().toString(); + } + + /** + * Gets list of resources which are locked by the client + * @return + */ + public List getLockedResources() { + return new ArrayList<>(resourceKeyMap.keySet()); + } + + /** + * Release the lock (if held) on the resource + * @param resource + */ + public void releaseLock(String resource) { + if (!checkResourceExists(resource)) { + logger.info("No lock held on {}", resource); + return; + } + CountDownLatch latch = new CountDownLatch(votingHostsSelector.getVotingSize()); + votingHostsSelector.getVotingHosts().getEntireList().stream() + .map(host -> new CheckAndRunHost(host, pool, "del", resource, resourceKeyMap.get(resource))) + .forEach(ulH -> CompletableFuture.supplyAsync(ulH, service) + .thenAccept(result -> latch.countDown()) + ); + boolean latchValue = false; + try { + latchValue = latch.await(timeout, timeoutUnit); + } catch (InterruptedException e) { + logger.info("Interrupted while releasing the lock for resource {}", resource); + } + if (latchValue) { + logger.info("Released lock on {}", resource); + } else { + logger.info("Timed out before we could release the lock"); + } + resourceKeyMap.remove(resource); + } + + /** + * Return a timer task which will recursively schedule itself when extension is successful. + * @param runJob + * @param resource + * @param ttlMS + * @param extensionFailed - This function gets called with the resource name when extension was unsuccessful. + * @return + */ + private TimerTask getExtensionTask(Timer runJob, String resource, long ttlMS, Consumer extensionFailed) { + return new TimerTask() { + @Override + public void run() { + long extendedValue = extendLock(resource, ttlMS); + if (extendedValue > 0) { + logger.info("Extended lock on {} for {} MS", resource, ttlMS); + TimerTask task = getExtensionTask(runJob, resource, ttlMS, extensionFailed); + runJob.schedule(task, extendedValue/2); + return; + } + extensionFailed.accept(resource); + } + }; + } + + /** + * Try to acquire lock on resource for ttlMS and keep extending it by ttlMS when its about to expire. + * Calls the failure function with the resource when extension fails. + * @param resource The resource on which you want to acquire a lock + * @param ttlMS The amount of time for which we need to acquire the lock. We try to extend the lock every ttlMS / 2 + * @param failure This function will be called with the resource which could not be locked. This function is called + * even if the client released the lock. + * @return returns true if we were able to successfully acqurie the lock. + */ + public boolean acquireLock(String resource, long ttlMS, Consumer failure) { + return acquireLockWithExtension(resource, ttlMS, (r) -> { + releaseLock(r); + failure.accept(r); + }); + } + + /** + * Try to acquire the lock and schedule extension jobs recursively until extension fails. + * @param resource + * @param ttlMS + * @param extensionFailedCallback - gets called with the resource name when extension fails. + * @return + */ + private boolean acquireLockWithExtension(String resource, long ttlMS, Consumer extensionFailedCallback) { + long acquireResult = acquireLock(resource, ttlMS); + if (acquireResult > 0) { + Timer runJob = new Timer(resource, true); + runJob.schedule(getExtensionTask(runJob, resource, ttlMS, extensionFailedCallback), acquireResult/2); + return true; + } + return false; + } + + /** + * This function is used to acquire / extend the lock on at least quorum number of hosts + * @param resource + * @param ttlMS + * @param extend + * @return + */ + private long runLockHost(String resource, long ttlMS, boolean extend) { + long startTime = Instant.now().toEpochMilli(); + long drift = Math.round(ttlMS * CLOCK_DRIFT) + 2; + LockResource lockResource = new LockResource(resource, ttlMS); + CountDownLatch latch = new CountDownLatch(quorum); + if (extend) { + votingHostsSelector.getVotingHosts().getEntireList().stream() + .map(host -> new ExtendHost(host, pool, lockResource, latch, resourceKeyMap.get(resource))) + .forEach(lH -> CompletableFuture.supplyAsync(lH, service)); + } else { + votingHostsSelector.getVotingHosts().getEntireList().stream() + .map(host -> new LockHost(host, pool, lockResource, latch, resourceKeyMap.get(resource))) + .forEach(lH -> CompletableFuture.supplyAsync(lH, service)); + } + awaitLatch(latch, resource); + long validity = 0L; + if (lockResource.getLocked() >= quorum) { + long timeElapsed = Instant.now().toEpochMilli() - startTime; + validity = ttlMS - timeElapsed - drift; + } else { + releaseLock(resource); + } + return validity; + } + + /** + * Tries to acquire lock on resource for ttlMS milliseconds. Returns the amount of time for which the lock was acquired + * @param resource + * @param ttlMS + * @return + */ + public long acquireLock(String resource, long ttlMS) { + resourceKeyMap.putIfAbsent(resource, getRandomString()); + return runLockHost(resource, ttlMS, false); + } + + /** + * Check if we still have the lock on a resource + * @param resource + * @return + */ + boolean checkResourceExists(String resource) { + if (!resourceKeyMap.containsKey(resource)) { + logger.info("No lock held on {}", resource); + return false; + } else { + return true; + } + } + + private boolean awaitLatch(CountDownLatch latch, String resource) { + try { + return latch.await(timeout, timeoutUnit); + } catch (InterruptedException e) { + logger.info("Interrupted while checking the lock for resource {}", resource); + return false; + } + } + + /** + * Check and get the ttls for the lock if it exists. This returns the minimum of ttls returned across the hosts + * @param resource + * @return + */ + public long checkLock(String resource) { + if (!checkResourceExists(resource)) { + return 0; + } else { + long startTime = Instant.now().toEpochMilli(); + CopyOnWriteArrayList resultTtls = new CopyOnWriteArrayList<>(); + CountDownLatch latch = new CountDownLatch(quorum); + votingHostsSelector.getVotingHosts().getEntireList().stream() + .map(host -> new CheckAndRunHost(host, pool, "pttl", resource, resourceKeyMap.get(resource))) + .forEach(checkAndRunHost -> CompletableFuture.supplyAsync(checkAndRunHost, service) + .thenAccept(r -> { + String result = r.getResult().toString(); + // The lua script returns 0 if we have lost the lock or we get -2 if the ttl expired on + // the key when we checked for the pttl. + if (result.equals("0") || result.equals("-2")) { + logger.info("Lock not present on host"); + } else { + resultTtls.add(Long.valueOf(result)); + latch.countDown(); + } + }) + ); + boolean latchValue = awaitLatch(latch, resource); + if (latchValue) { + long timeElapsed = Instant.now().toEpochMilli() - startTime; + logger.info("Checked lock on {}", resource); + return Collections.min(resultTtls) - timeElapsed; + } else { + logger.info("Timed out before we could check the lock"); + return 0; + } + } + } + + /** + * Try to extend lock by ttlMS + * @param resource + * @param ttlMS + * @return + */ + public long extendLock(String resource, long ttlMS) { + if (!checkResourceExists(resource)) { + logger.info("Could not extend lock since its already released"); + return 0; + } else { + return runLockHost(resource, ttlMS, true); + } + } + + /** + * Release all locks to clean. + */ + public void cleanup() { + resourceKeyMap.keySet().stream().forEach(this::releaseLock); + } + + /** + * Log all the lock resources held right now. + */ + public void logLocks() { + resourceKeyMap.entrySet().stream() + .forEach(e -> logger.info("Resource: {}, Key: {}", e.getKey(), e.getValue())); + } + + public static class Builder { + private String appName; + private String clusterName; + private TokenMapSupplier tokenMapSupplier; + private HostSupplier hostSupplier; + private ConnectionPoolConfigurationImpl cpConfig; + private EurekaClient eurekaClient; + private long timeout; + private TimeUnit timeoutUnit; + + public Builder() { + } + + public Builder withTimeout(long timeout) { + this.timeout = timeout; + return this; + } + + public Builder withTimeoutUnit(TimeUnit unit) { + this.timeoutUnit = unit; + return this; + } + + public Builder withEurekaClient(EurekaClient eurekaClient) { + this.eurekaClient = eurekaClient; + return this; + } + + public Builder withApplicationName(String applicationName) { + appName = applicationName; + return this; + } + + public Builder withDynomiteClusterName(String cluster) { + clusterName = cluster; + return this; + } + + public Builder withHostSupplier(HostSupplier hSupplier) { + hostSupplier = hSupplier; + return this; + } + + public Builder withTokenMapSupplier(TokenMapSupplier tokenMapSupplier) { + this.tokenMapSupplier = tokenMapSupplier; + return this; + } + + public Builder withConnectionPoolConfiguration(ConnectionPoolConfigurationImpl cpConfig) { + this.cpConfig = cpConfig; + return this; + } + + public DynoLockClient build() { + assert (appName != null); + assert (clusterName != null); + + if (cpConfig == null) { + cpConfig = new ArchaiusConnectionPoolConfiguration(appName); + logger.info("Dyno Client runtime properties: " + cpConfig.toString()); + } + + // We do not want to fallback to other azs which is the normal opertion for the connection pool + cpConfig.setFallbackEnabled(false); + cpConfig.setConnectToDatastore(true); + + return buildDynoLockClient(); + } + + private DynoLockClient buildDynoLockClient() { + DynoOPMonitor opMonitor = new DynoOPMonitor(appName); + ConnectionPoolMonitor cpMonitor = new DynoCPMonitor(appName); + + DynoJedisUtils.updateConnectionPoolConfig(cpConfig, hostSupplier, tokenMapSupplier, eurekaClient, + clusterName); + if (tokenMapSupplier == null) + tokenMapSupplier = cpConfig.getTokenSupplier(); + final ConnectionPool pool = DynoJedisUtils.createConnectionPool(appName, opMonitor, cpMonitor, + cpConfig, null); + VotingHostsFromTokenRange votingHostSelector = new VotingHostsFromTokenRange(hostSupplier, tokenMapSupplier, + cpConfig.getLockVotingSize()); + + return new DynoLockClient(pool, votingHostSelector, timeout, timeoutUnit); + } + } +} diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/LockResource.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/LockResource.java new file mode 100644 index 00000000..3b364cb3 --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/LockResource.java @@ -0,0 +1,30 @@ +package com.netflix.dyno.recipes.lock; + +import java.util.concurrent.atomic.AtomicInteger; + +public class LockResource { + private final AtomicInteger locked = new AtomicInteger(0); + private final String resource; + private final long ttlMs; + + public LockResource(String resource, long ttlMs) { + this.resource = resource; + this.ttlMs = ttlMs; + } + + public String getResource() { + return resource; + } + + public long getTtlMs() { + return ttlMs; + } + + public int getLocked() { + return locked.get(); + } + + public int incrementLocked() { + return locked.incrementAndGet(); + } +} diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRange.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRange.java new file mode 100644 index 00000000..775d8bee --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRange.java @@ -0,0 +1,92 @@ +package com.netflix.dyno.recipes.lock; + +import com.google.common.collect.ImmutableSet; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostSupplier; +import com.netflix.dyno.connectionpool.TokenMapSupplier; +import com.netflix.dyno.connectionpool.impl.lb.CircularList; +import com.netflix.dyno.connectionpool.impl.lb.HostToken; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * This class deterministically returns a list of hosts which will be used for voting. We use the TokenRange to get the + * same set of hosts from all clients. + */ +public class VotingHostsFromTokenRange implements VotingHostsSelector { + + private final TokenMapSupplier tokenMapSupplier; + private final HostSupplier hostSupplier; + private final CircularList votingHosts = new CircularList<>(new ArrayList<>()); + private final int MIN_VOTING_SIZE = 1; + private final int MAX_VOTING_SIZE = 5; + private final int effectiveVotingSize; + private final AtomicInteger calculatedVotingSize = new AtomicInteger(0); + + public VotingHostsFromTokenRange(HostSupplier hostSupplier, TokenMapSupplier tokenMapSupplier, int votingSize) { + this.tokenMapSupplier = tokenMapSupplier; + this.hostSupplier = hostSupplier; + effectiveVotingSize = votingSize == -1 ? MAX_VOTING_SIZE : votingSize; + if(votingSize % 2 == 0) { + throw new IllegalStateException("Cannot perform voting with even number of hosts"); + } + getVotingHosts(); + } + + @Override + public CircularList getVotingHosts() { + if (votingHosts.getSize() == 0) { + if(effectiveVotingSize % 2 == 0) { + throw new IllegalStateException("Cannot do voting with even number of nodes for voting"); + } + List allHostTokens = tokenMapSupplier.getTokens(ImmutableSet.copyOf(hostSupplier.getHosts())); + if (allHostTokens.size() < MIN_VOTING_SIZE) { + throw new IllegalStateException(String.format("Cannot perform voting with less than %d nodes", MIN_VOTING_SIZE)); + } + // Total number of hosts present per rack + Map numHostsPerRack = allHostTokens.stream().map(ht -> ht.getHost().getRack()).collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + AtomicInteger numHostsRequired = new AtomicInteger(effectiveVotingSize); + // Map to keep track of number of hosts to take for voting from this rack + Map numHosts = new HashMap<>(); + // Sort racks to get the same order + List racks = numHostsPerRack.keySet().stream().sorted(Comparator.comparing(String::toString)).collect(Collectors.toList()); + for(String rack: racks) { + // Take as many hosts as you can from this rack. + int v = (int) Math.min(numHostsRequired.get(), numHostsPerRack.get(rack)); + numHostsRequired.addAndGet(-v); + numHosts.put(rack, v); + calculatedVotingSize.addAndGet(v); + } + if(calculatedVotingSize.get() % 2 == 0) { + throw new IllegalStateException("Could not construct voting pool. Min number of hosts not met!"); + } + Map> rackToHostToken = allHostTokens.stream() + .collect(Collectors.groupingBy(ht -> ht.getHost().getRack())); + // Get the final list of voting hosts + List finalVotingHosts = rackToHostToken.entrySet().stream() + // Sorting on token to get hosts deterministically. + .sorted(Comparator.comparing(Map.Entry::getKey)) + .flatMap(e -> { + List temp = e.getValue(); + temp.sort(HostToken::compareTo); + return temp.subList(0, numHosts.get(e.getKey())).stream(); + }) + .map(ht -> ht.getHost()) + .collect(Collectors.toList()); + votingHosts.swapWithList(finalVotingHosts); + } + return votingHosts; + } + + @Override + public int getVotingSize() { + return calculatedVotingSize.get(); + } +} diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsSelector.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsSelector.java new file mode 100644 index 00000000..a2e8b8c4 --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/VotingHostsSelector.java @@ -0,0 +1,18 @@ +package com.netflix.dyno.recipes.lock; + +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.impl.lb.CircularList; + +public interface VotingHostsSelector { + /** + * Get the list of hosts eligible for voting + * @return + */ + CircularList getVotingHosts(); + + /** + * Returns the number of voting hosts + * @return + */ + int getVotingSize(); +} diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CheckAndRunHost.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CheckAndRunHost.java new file mode 100644 index 00000000..5404b655 --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CheckAndRunHost.java @@ -0,0 +1,53 @@ +package com.netflix.dyno.recipes.lock.command; + +import com.netflix.dyno.connectionpool.Connection; +import com.netflix.dyno.connectionpool.ConnectionContext; +import com.netflix.dyno.connectionpool.ConnectionPool; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.OperationResult; +import com.netflix.dyno.jedis.OpName; +import com.netflix.dyno.jedis.operation.BaseKeyOperation; +import redis.clients.jedis.Jedis; + +/** + * Runs a command against the host and is used to remove the lock and checking the ttl on the resource + */ +public class CheckAndRunHost extends CommandHost { + + private static final String cmdScript = " if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" + + " return redis.call(\"%s\",KEYS[1])\n" + + " else\n" + + " return 0\n" + + " end"; + + private final String resource; + + private final String randomKey; + + private final String command; + + public CheckAndRunHost(Host host, ConnectionPool pool, String command, String resource, String randomKey) { + super(host, pool); + this.command = String.format(cmdScript, command); + this.resource = resource; + this.randomKey = randomKey; + } + + + @Override + public OperationResult get() { + Connection connection = getConnection(); + OperationResult result = connection.execute(new BaseKeyOperation(randomKey, OpName.EVAL) { + @Override + public Object execute(Jedis client, ConnectionContext state) { + if (randomKey == null) { + throw new IllegalStateException("Cannot extend lock with null value for key"); + } + Object result = client.eval(command, 1, resource, randomKey); + return result; + } + }); + cleanConnection(connection); + return result; + } +} diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CommandHost.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CommandHost.java new file mode 100644 index 00000000..548427e8 --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/CommandHost.java @@ -0,0 +1,35 @@ +package com.netflix.dyno.recipes.lock.command; + +import com.netflix.dyno.connectionpool.Connection; +import com.netflix.dyno.connectionpool.ConnectionPool; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostConnectionPool; +import com.netflix.dyno.connectionpool.OperationResult; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * This class is used to handle the host connection startup and cleanup. + * All non abstract subclasses should implement the supplier operation. + * @param + */ +public abstract class CommandHost implements Supplier> { + private final Host host; + private final ConnectionPool pool; + + public CommandHost(Host host, ConnectionPool pool) { + this.host = host; + this.pool = pool; + } + + public Connection getConnection() { + HostConnectionPool hostPool = pool.getHostPool(host); + return hostPool.borrowConnection(pool.getConfiguration().getMaxTimeoutWhenExhausted(), TimeUnit.MILLISECONDS); + } + + public void cleanConnection(Connection connection) { + connection.getContext().reset(); + connection.getParentConnectionPool().returnConnection(connection); + } +} diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/ExtendHost.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/ExtendHost.java new file mode 100644 index 00000000..d7112aa5 --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/ExtendHost.java @@ -0,0 +1,61 @@ +package com.netflix.dyno.recipes.lock.command; + +import com.netflix.dyno.connectionpool.Connection; +import com.netflix.dyno.connectionpool.ConnectionContext; +import com.netflix.dyno.connectionpool.ConnectionPool; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.OperationResult; +import com.netflix.dyno.jedis.OpName; +import com.netflix.dyno.jedis.operation.BaseKeyOperation; +import com.netflix.dyno.recipes.lock.LockResource; +import redis.clients.jedis.Jedis; + +import java.util.concurrent.CountDownLatch; + +/** + * Instances of this class should be used to perform extend operations on an acquired lock. + */ +public class ExtendHost extends CommandHost { + + private static final String cmdScript = " if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" + + " return redis.call(\"set\",KEYS[1], ARGV[1], \"px\", ARGV[2])" + + " else\n" + + " return 0\n" + + " end"; + private final LockResource lockResource; + private final String value; + private final String randomKey; + private final CountDownLatch latch; + + public ExtendHost(Host host, ConnectionPool pool, LockResource lockResource, CountDownLatch latch, String randomKey) { + super(host, pool); + this.lockResource = lockResource; + this.value = lockResource.getResource(); + this.randomKey = randomKey; + this.latch = latch; + } + + @Override + public OperationResult get() { + Connection connection = getConnection(); + OperationResult result = connection.execute(new BaseKeyOperation(randomKey, OpName.EVAL) { + @Override + public LockResource execute(Jedis client, ConnectionContext state) { + // We need to recheck randomKey in case it got removed before we get here. + if (randomKey == null) { + throw new IllegalStateException("Cannot extend lock with null value for key"); + } + String result = client.eval(cmdScript, 1, value, randomKey, String.valueOf(lockResource.getTtlMs())) + .toString(); + if (result.equals("OK")) { + lockResource.incrementLocked(); + latch.countDown(); + } + return lockResource; + } + }); + cleanConnection(connection); + return result; + } +} + diff --git a/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/LockHost.java b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/LockHost.java new file mode 100644 index 00000000..d4fdd672 --- /dev/null +++ b/dyno-recipes/src/main/java/com/netflix/dyno/recipes/lock/command/LockHost.java @@ -0,0 +1,51 @@ +package com.netflix.dyno.recipes.lock.command; + +import com.netflix.dyno.connectionpool.Connection; +import com.netflix.dyno.connectionpool.ConnectionContext; +import com.netflix.dyno.connectionpool.ConnectionPool; +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.OperationResult; +import com.netflix.dyno.jedis.OpName; +import com.netflix.dyno.jedis.operation.BaseKeyOperation; +import com.netflix.dyno.recipes.lock.LockResource; +import redis.clients.jedis.Jedis; + +import java.util.concurrent.CountDownLatch; + +/** + * This class is used to acquire the lock on a host with a resource. + */ +public class LockHost extends CommandHost { + + private final String value; + private final LockResource lockResource; + + private final String randomKey; + private final CountDownLatch latch; + + public LockHost(Host host, ConnectionPool pool, LockResource lockResource, CountDownLatch latch, String randomKey) { + super(host, pool); + this.lockResource = lockResource; + this.value = lockResource.getResource(); + this.randomKey = randomKey; + this.latch = latch; + } + + @Override + public OperationResult get() { + Connection connection = getConnection(); + OperationResult result = connection.execute(new BaseKeyOperation(value, OpName.SET) { + @Override + public LockResource execute(Jedis client, ConnectionContext state) { + String result = client.set(value, randomKey, "NX", "PX", lockResource.getTtlMs()); + if (result != null && result.equals("OK")) { + lockResource.incrementLocked(); + latch.countDown(); + } + return lockResource; + } + }); + cleanConnection(connection); + return result; + } +} diff --git a/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/DynoLockClientTest.java b/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/DynoLockClientTest.java new file mode 100644 index 00000000..18c7ab20 --- /dev/null +++ b/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/DynoLockClientTest.java @@ -0,0 +1,156 @@ +package com.netflix.dyno.recipes.lock; + +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.TokenMapSupplier; +import com.netflix.dyno.connectionpool.impl.lb.HostToken; +import com.netflix.dyno.recipes.util.Tuple; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.IntStream; + +public abstract class DynoLockClientTest { + + Host host; + TokenMapSupplierImpl tokenMapSupplier; + DynoLockClient dynoLockClient; + String resource = "testResource"; + + public abstract DynoLockClient constructDynoLockClient(); + + @After + public void releaseLock() { + dynoLockClient.releaseLock(resource); + } + + @Test + public void testAcquireLockWithExtension() throws InterruptedException { + boolean acquireResult = dynoLockClient.acquireLock(resource, 500, (rsc) -> {}); + Assert.assertTrue("Failed to acquire lock on resource", acquireResult); + Thread.sleep(3000); + Assert.assertTrue(dynoLockClient.checkLock(resource) > 0); + dynoLockClient.releaseLock(resource); + Assert.assertTrue(dynoLockClient.checkLock(resource) == 0); + } + + @Test + public void testExtendLockAndCheckResourceExists() { + long v = dynoLockClient.acquireLock(resource, 500); + Assert.assertTrue("Acquire lock did not succeed in time", v > 0); + Assert.assertEquals(1, dynoLockClient.getLockedResources().size()); + Assert.assertTrue(dynoLockClient.checkResourceExists(resource)); + long ev = dynoLockClient.extendLock(resource, 1000); + Assert.assertTrue("Extend lock did not extend the lock", ev > 500); + dynoLockClient.releaseLock(resource); + Assert.assertEquals(0, dynoLockClient.getLockedResources().size()); + } + + @Test + public void testReleaseLock() { + long v = dynoLockClient.acquireLock(resource, 100); + Assert.assertTrue("Acquire lock did not succeed in time", v > 0); + dynoLockClient.releaseLock(resource); + v = dynoLockClient.checkLock(resource); + Assert.assertTrue("Release lock failed",v == 0); + } + + @Test + public void testExtendLockFailsIfTooLate() throws InterruptedException { + long v = dynoLockClient.acquireLock(resource, 100); + Assert.assertTrue("Acquire lock did not succeed in time", v > 0); + Assert.assertEquals(1, dynoLockClient.getLockedResources().size()); + Thread.sleep(100); + long ev = dynoLockClient.extendLock(resource, 1000); + Assert.assertTrue("Extend lock extended the lock even when late", ev == 0); + Assert.assertEquals(0, dynoLockClient.getLockedResources().size()); + } + + @Test + public void testCheckLock() { + long v = dynoLockClient.acquireLock(resource, 5000); + Assert.assertTrue("Acquire lock did not succeed in time", v > 0); + Assert.assertEquals(1, dynoLockClient.getLockedResources().size()); + v = dynoLockClient.checkLock(resource); + Assert.assertTrue("Check lock failed for acquired lock",v > 0); + dynoLockClient.releaseLock(resource); + Assert.assertTrue("Check lock failed for acquired lock", dynoLockClient.checkLock(resource) == 0); + } + + @Test + public void testLockClient() { + long v = dynoLockClient.acquireLock(resource, 1000); + Assert.assertTrue("Acquire lock did not succeed in time", v > 0); + Assert.assertEquals(1, dynoLockClient.getLockedResources().size()); + dynoLockClient.releaseLock(resource); + Assert.assertEquals(0, dynoLockClient.getLockedResources().size()); + } + + @Test + public void testLockClientConcurrent() { + DynoLockClient[] cs = new DynoLockClient[] {constructDynoLockClient(), constructDynoLockClient(), constructDynoLockClient()}; + CopyOnWriteArrayList clients = new CopyOnWriteArrayList<>(cs); + List ttls = Arrays.asList(new Long[]{1000L, 500L, 250L}); + AtomicInteger count = new AtomicInteger(3); + Collections.shuffle(ttls); + ConcurrentLinkedDeque ttlQueue = new ConcurrentLinkedDeque<>(ttls); + List resultList = Collections.synchronizedList(new ArrayList()); + Supplier> acquireLock = () -> { + long ttl = ttlQueue.poll(); + long value = clients.get(count.decrementAndGet()).acquireLock(resource, ttl); + resultList.add(value); + return new Tuple<>(ttl, value); + }; + IntStream.range(0, ttls.size()).mapToObj(i -> CompletableFuture.supplyAsync(acquireLock) + .thenAccept(t -> Assert.assertTrue(t._2() < t._1()))).forEach(f -> { + try { + f.get(); + } catch (InterruptedException e) { + Assert.fail("Interrupted during the test"); + } catch (ExecutionException e) { + e.printStackTrace(); + Assert.fail(); + } + }); + boolean lock = false; + for(Long r: resultList) { + if(r > 0) { + if(lock) { + Assert.fail("Lock did not work as expected " + Arrays.toString(resultList.toArray())); + } + lock = true; + } + } + } + + static class TokenMapSupplierImpl implements TokenMapSupplier { + + private final HostToken localHostToken; + + TokenMapSupplierImpl(Host host) { + this.localHostToken = new HostToken(100000L, host); + } + + @Override + public List getTokens(Set activeHosts) { + return Collections.singletonList(localHostToken); + } + + @Override + public HostToken getTokenForHost(Host host, Set activeHosts) { + return localHostToken; + } + + } +} \ No newline at end of file diff --git a/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/LocalRedisLockTest.java b/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/LocalRedisLockTest.java new file mode 100644 index 00000000..2493dac4 --- /dev/null +++ b/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/LocalRedisLockTest.java @@ -0,0 +1,61 @@ +package com.netflix.dyno.recipes.lock; + +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; +import com.netflix.dyno.connectionpool.HostSupplier; +import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl; +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import redis.embedded.RedisServer; + +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +public class LocalRedisLockTest extends DynoLockClientTest { + private static final int REDIS_PORT = 8999; + private static final String REDIS_RACK = "rack-1c"; + private static final String REDIS_DATACENTER = "rack-1"; + + private RedisServer redisServer; + + @Before + public void setUp() throws IOException { + redisServer = new RedisServer(REDIS_PORT); + redisServer.start(); + Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")); + host = new HostBuilder().setHostname("localhost").setDatastorePort(REDIS_PORT).setPort(REDIS_PORT).setRack(REDIS_RACK).setStatus(Host.Status.Up).createHost(); + tokenMapSupplier = new TokenMapSupplierImpl(host); + dynoLockClient = constructDynoLockClient(); + } + + @After + public void tearDown() { + if (redisServer != null) { + redisServer.stop(); + } + } + + public DynoLockClient constructDynoLockClient() { + HostSupplier hostSupplier = () -> Collections.singletonList(host); + + final ConnectionPoolConfigurationImpl connectionPoolConfiguration = + new ConnectionPoolConfigurationImpl(REDIS_RACK); + connectionPoolConfiguration.withTokenSupplier(tokenMapSupplier); + connectionPoolConfiguration.setLocalRack(REDIS_RACK); + connectionPoolConfiguration.setLocalDataCenter(REDIS_DATACENTER); + connectionPoolConfiguration.setConnectToDatastore(true); + + return new DynoLockClient.Builder() + .withApplicationName("test") + .withDynomiteClusterName("testcluster") + .withHostSupplier(hostSupplier) + .withTokenMapSupplier(tokenMapSupplier) + .withTimeout(50) + .withConnectionPoolConfiguration(connectionPoolConfiguration) + .withTimeoutUnit(TimeUnit.MILLISECONDS) + .build(); + } + +} diff --git a/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRangeTest.java b/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRangeTest.java new file mode 100644 index 00000000..8def4db1 --- /dev/null +++ b/dyno-recipes/src/test/java/com/netflix/dyno/recipes/lock/VotingHostsFromTokenRangeTest.java @@ -0,0 +1,78 @@ +package com.netflix.dyno.recipes.lock; + +import com.netflix.dyno.connectionpool.Host; +import com.netflix.dyno.connectionpool.HostBuilder; +import com.netflix.dyno.connectionpool.HostSupplier; +import com.netflix.dyno.connectionpool.TokenMapSupplier; +import com.netflix.dyno.connectionpool.impl.lb.CircularList; +import com.netflix.dyno.connectionpool.impl.lb.HostToken; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class VotingHostsFromTokenRangeTest { + + private String r1 = "rack1"; + private String r2 = "rack2"; + private String r3 = "rack3"; + private TokenMapSupplier tokenMapSupplier; + private HostSupplier hostSupplier; + private VotingHostsSelector votingHostsSelector; + private List hosts; + + @Before + public void setUp() { + Host h1 = new HostBuilder().setHostname("h1").setRack(r1).setStatus(Host.Status.Up).createHost(); + Host h2 = new HostBuilder().setHostname("h2").setRack(r1).setStatus(Host.Status.Up).createHost(); + Host h3 = new HostBuilder().setHostname("h3").setRack(r2).setStatus(Host.Status.Up).createHost(); + Host h4 = new HostBuilder().setHostname("h4").setRack(r2).setStatus(Host.Status.Up).createHost(); + Host h5 = new HostBuilder().setHostname("h5").setRack(r2).setStatus(Host.Status.Up).createHost(); + Host h6 = new HostBuilder().setHostname("h6").setRack(r3).setStatus(Host.Status.Up).createHost(); + + Host[] arr = {h1, h2, h3, h4, h5, h6}; + hosts = Arrays.asList(arr); + final Map tokenMap = new HashMap<>(); + + tokenMap.put(h1, new HostToken(1111L, h1)); + tokenMap.put(h2, new HostToken(2222L, h2)); + tokenMap.put(h3, new HostToken(1111L, h3)); + tokenMap.put(h4, new HostToken(2222L, h4)); + tokenMap.put(h5, new HostToken(3333L, h5)); + tokenMap.put(h6, new HostToken(1111L, h6)); + hostSupplier = () -> hosts; + tokenMapSupplier = new TokenMapSupplier() { + @Override + public List getTokens(Set activeHosts) { + return new ArrayList<>(tokenMap.values()); + } + + @Override + public HostToken getTokenForHost(Host host, Set activeHosts) { + return tokenMap.get(host); + } + }; + } + + private void testVotingSize(int votingSize) { + votingHostsSelector = new VotingHostsFromTokenRange(hostSupplier, tokenMapSupplier, votingSize); + CircularList hosts = votingHostsSelector.getVotingHosts(); + Set resultHosts = hosts.getEntireList().stream().map(h -> h.getHostName()).collect(Collectors.toSet()); + Assert.assertEquals(votingSize, resultHosts.size()); + Assert.assertEquals(votingSize, + hosts.getEntireList().subList(0, votingSize).stream().filter(h1 -> resultHosts.contains(h1.getHostName())).count()); + } + + @Test + public void getVotingSize() { + IntStream.range(1, 6).filter(i -> i%2 != 0).forEach(i -> testVotingSize(i)); + } +} \ No newline at end of file diff --git a/dyno-redisson/src/main/java/com/netflix/dyno/redisson/RedissonConnectionFactory.java b/dyno-redisson/src/main/java/com/netflix/dyno/redisson/RedissonConnectionFactory.java index 14127bb4..e3adac35 100644 --- a/dyno-redisson/src/main/java/com/netflix/dyno/redisson/RedissonConnectionFactory.java +++ b/dyno-redisson/src/main/java/com/netflix/dyno/redisson/RedissonConnectionFactory.java @@ -15,19 +15,12 @@ */ package com.netflix.dyno.redisson; -import io.netty.channel.EventLoopGroup; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicReference; - import com.lambdaworks.redis.RedisAsyncConnection; import com.lambdaworks.redis.RedisClient; import com.netflix.dyno.connectionpool.AsyncOperation; import com.netflix.dyno.connectionpool.Connection; import com.netflix.dyno.connectionpool.ConnectionContext; import com.netflix.dyno.connectionpool.ConnectionFactory; -import com.netflix.dyno.connectionpool.ConnectionObservor; import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostConnectionPool; import com.netflix.dyno.connectionpool.ListenableFuture; @@ -36,10 +29,14 @@ import com.netflix.dyno.connectionpool.OperationResult; import com.netflix.dyno.connectionpool.exception.DynoConnectException; import com.netflix.dyno.connectionpool.exception.DynoException; -import com.netflix.dyno.connectionpool.exception.ThrottledException; import com.netflix.dyno.connectionpool.impl.ConnectionContextImpl; import com.netflix.dyno.connectionpool.impl.FutureOperationalResultImpl; import com.netflix.dyno.connectionpool.impl.OperationResultImpl; +import io.netty.channel.EventLoopGroup; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; public class RedissonConnectionFactory implements ConnectionFactory> { @@ -52,10 +49,15 @@ public RedissonConnectionFactory(EventLoopGroup group, OperationMonitor operatio } @Override - public Connection> createConnection(HostConnectionPool> pool, ConnectionObservor connectionObservor) throws DynoConnectException, ThrottledException { + public Connection> createConnection(HostConnectionPool> pool) throws DynoConnectException { return new RedissonConnection(pool, eventGroupLoop, opMonitor); } + @Override + public Connection> createConnectionWithDataStore(HostConnectionPool> pool) throws DynoConnectException { + throw new UnsupportedOperationException(""); + } + public static class RedissonConnection implements Connection> { private final HostConnectionPool> hostPool; diff --git a/gradlew b/gradlew index cccdd3d5..05feddf6 100755 --- a/gradlew +++ b/gradlew @@ -30,7 +30,7 @@ APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" -# Use the maximum available, or set MAX_FD != -1 to use that value. +# Use the maximum available, or set MAX_FD != -1 to use that resource. MAX_FD="maximum" warn () {