From c662c46d027bf81e79d1e6327dfcfe368fa96285 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 13 Feb 2015 08:36:12 +0100 Subject: [PATCH] DATAREDIS-372 - Allow easier RedisSentinelConfiguration setup. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now allow to initialize a RedisSentinelConfiguration based on a given String collection of host:ports. sentinelHostAndPorts[0] = 127.0.0.1:23679 sentinelHostAndPorts[1] = 127.0.0.1:23680 Further on it’s possible to pass in a PropertySource containing master name and sentinel nodes. spring.redis.sentinel.master=myMaster spring.redis.sentinel.nodes=127.0.0.1:23679,127.0.0.1:23680 Original pull request: #126. --- .../RedisSentinelConfiguration.java | 104 +++++++++++- .../RedisSentinelConfigurationUnitTests.java | 150 ++++++++++++++++++ 2 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java index 2a2571ecf..9e7fcb49b 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,18 @@ */ package org.springframework.data.redis.connection; +import static org.springframework.util.Assert.*; +import static org.springframework.util.StringUtils.*; + import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; -import org.springframework.util.Assert; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.util.StringUtils; /** * Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using connecting @@ -27,10 +34,14 @@ import org.springframework.util.Assert; * environment. * * @author Christoph Strobl + * @author Thomas Darimont * @since 1.4 */ public class RedisSentinelConfiguration { + private static final String REDIS_SENTINEL_MASTER_CONFIG_PROPERTY = "spring.redis.sentinel.master"; + private static final String REDIS_SENTINEL_NODES_CONFIG_PROPERTY = "spring.redis.sentinel.nodes"; + private NamedNode master; private Set sentinels; @@ -38,7 +49,53 @@ public class RedisSentinelConfiguration { * Creates new {@link RedisSentinelConfiguration}. */ public RedisSentinelConfiguration() { + this(new MapPropertySource("RedisSentinelConfiguration", Collections. emptyMap())); + } + + /** + * Creates {@link RedisSentinelConfiguration} for given hostPort combinations. + * + *
+	 * sentinelHostAndPorts[0] = 127.0.0.1:23679
+	 * sentinelHostAndPorts[1] = 127.0.0.1:23680
+	 * ...
+	 * 
+	 * 
+	 * 
+	 * @param sentinelHostAndPorts must not be {@literal null}.
+	 * @since 1.5
+	 */
+	public RedisSentinelConfiguration(String master, Set sentinelHostAndPorts) {
+		this(new MapPropertySource("RedisSentinelConfiguration", asMap(master, sentinelHostAndPorts)));
+	}
+
+	/**
+	 * Creates {@link RedisSentinelConfiguration} looking up values in given {@link PropertySource}.
+	 * 
+	 * 
+	 * 
+	 * spring.redis.sentinel.master=myMaster
+	 * spring.redis.sentinel.nodes=127.0.0.1:23679,127.0.0.1:23680,127.0.0.1:23681
+	 * 
+	 * 
+ * + * @param propertySource must not be {@literal null}. + * @since 1.5 + */ + public RedisSentinelConfiguration(PropertySource propertySource) { + + notNull(propertySource, "PropertySource must not be null!"); + this.sentinels = new LinkedHashSet(); + + if (propertySource.containsProperty(REDIS_SENTINEL_MASTER_CONFIG_PROPERTY)) { + this.setMaster(propertySource.getProperty(REDIS_SENTINEL_MASTER_CONFIG_PROPERTY).toString()); + } + + if (propertySource.containsProperty(REDIS_SENTINEL_NODES_CONFIG_PROPERTY)) { + appendSentinels(commaDelimitedListToSet(propertySource.getProperty(REDIS_SENTINEL_NODES_CONFIG_PROPERTY) + .toString())); + } } /** @@ -48,8 +105,10 @@ public class RedisSentinelConfiguration { */ public void setSentinels(Iterable sentinels) { - Assert.notNull(sentinels, "Cannot set sentinels to 'null'."); + notNull(sentinels, "Cannot set sentinels to 'null'."); + this.sentinels.clear(); + for (RedisNode sentinel : sentinels) { addSentinel(sentinel); } @@ -71,7 +130,7 @@ public class RedisSentinelConfiguration { */ public void addSentinel(RedisNode sentinel) { - Assert.notNull(sentinel, "Sentinel must not be 'null'."); + notNull(sentinel, "Sentinel must not be 'null'."); this.sentinels.add(sentinel); } @@ -82,7 +141,7 @@ public class RedisSentinelConfiguration { */ public void setMaster(final String name) { - Assert.notNull(name, "Name of sentinel master must not be null."); + notNull(name, "Name of sentinel master must not be null."); setMaster(new NamedNode() { @Override @@ -99,7 +158,7 @@ public class RedisSentinelConfiguration { */ public void setMaster(NamedNode master) { - Assert.notNull("Sentinel master node must not be 'null'."); + notNull("Sentinel master node must not be 'null'."); this.master = master; } @@ -151,4 +210,37 @@ public class RedisSentinelConfiguration { public RedisSentinelConfiguration sentinel(String host, Integer port) { return sentinel(new RedisNode(host, port)); } + + private void appendSentinels(Set hostAndPorts) { + + for (String hostAndPort : hostAndPorts) { + addSentinel(readHostAndPortFromString(hostAndPort)); + } + } + + private RedisNode readHostAndPortFromString(String hostAndPort) { + + String[] args = split(hostAndPort, ":"); + + notNull(args, "HostAndPort need to be seperated by ':'."); + isTrue(args.length == 2, "Host and Port String needs to specified as host:port"); + return new RedisNode(args[0], Integer.valueOf(args[1]).intValue()); + } + + /** + * @param master must not be {@literal null} or empty. + * @param sentinelHostAndPorts must not be {@literal null}. + * @return + */ + private static Map asMap(String master, Set sentinelHostAndPorts) { + + hasText(master, "Master address must not be null or empty!"); + notNull(sentinelHostAndPorts, "SentinelHostAndPorts must not be null!"); + + Map map = new HashMap(); + map.put(REDIS_SENTINEL_MASTER_CONFIG_PROPERTY, master); + map.put(REDIS_SENTINEL_NODES_CONFIG_PROPERTY, StringUtils.collectionToCommaDelimitedString(sentinelHostAndPorts)); + + return map; + } } diff --git a/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java new file mode 100644 index 000000000..c4e2ac0ff --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java @@ -0,0 +1,150 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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 org.springframework.data.redis.connection; + +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsCollectionContaining.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; + +import org.junit.Test; +import org.springframework.core.env.PropertySource; +import org.springframework.mock.env.MockPropertySource; +import org.springframework.util.StringUtils; + +/** + * @author Christoph Strobl + */ +public class RedisSentinelConfigurationUnitTests { + + static final String HOST_AND_PORT_1 = "127.0.0.1:123"; + static final String HOST_AND_PORT_2 = "localhost:456"; + static final String HOST_AND_PORT_3 = "localhost:789"; + static final String HOST_AND_NO_PORT = "localhost"; + + /** + * @see DATAREDIS-372 + */ + @Test + public void shouldCreateRedisSentinelConfigurationCorrectlyGivenMasterAndSingleHostAndPortString() { + + RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", + Collections.singleton(HOST_AND_PORT_1)); + + assertThat(config.getSentinels().size(), is(1)); + assertThat(config.getSentinels(), hasItems(new RedisNode("127.0.0.1", 123))); + } + + /** + * @see DATAREDIS-372 + */ + @Test + public void shouldCreateRedisSentinelConfigurationCorrectlyGivenMasterAndMultipleHostAndPortStrings() { + + RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", new HashSet(Arrays.asList( + HOST_AND_PORT_1, HOST_AND_PORT_2, HOST_AND_PORT_3))); + + assertThat(config.getSentinels().size(), is(3)); + assertThat(config.getSentinels(), + hasItems(new RedisNode("127.0.0.1", 123), new RedisNode("localhost", 456), new RedisNode("localhost", 789))); + } + + /** + * @see DATAREDIS-372 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExecptionOnInvalidHostAndPortString() { + new RedisSentinelConfiguration("mymaster", Collections.singleton(HOST_AND_NO_PORT)); + } + + /** + * @see DATAREDIS-372 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionWhenListOfHostAndPortIsNull() { + new RedisSentinelConfiguration("mymaster", Collections. singleton(null)); + } + + /** + * @see DATAREDIS-372 + */ + @Test + public void shouldNotFailWhenListOfHostAndPortIsEmpty() { + + RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", Collections. emptySet()); + + assertThat(config.getSentinels().size(), is(0)); + } + + /** + * @see DATAREDIS-372 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionGivenNullPropertySource() { + new RedisSentinelConfiguration((PropertySource) null); + } + + /** + * @see DATAREDIS-372 + */ + @Test + public void shouldNotFailWhenGivenPropertySourceNotContainingRelevantProperties() { + + RedisSentinelConfiguration config = new RedisSentinelConfiguration(new MockPropertySource()); + + assertThat(config.getMaster(), nullValue()); + assertThat(config.getSentinels().size(), is(0)); + } + + /** + * @see DATAREDIS-372 + */ + @Test + public void shouldBeCreatedCorrecltyGivenValidPropertySourceWithMasterAndSingleHostPort() { + + MockPropertySource propertySource = new MockPropertySource(); + propertySource.setProperty("spring.redis.sentinel.master", "myMaster"); + propertySource.setProperty("spring.redis.sentinel.nodes", HOST_AND_PORT_1); + + RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource); + + assertThat(config.getMaster().getName(), is("myMaster")); + assertThat(config.getSentinels().size(), is(1)); + assertThat(config.getSentinels(), hasItems(new RedisNode("127.0.0.1", 123))); + } + + /** + * @see DATAREDIS-372 + */ + @Test + public void shouldBeCreatedCorrecltyGivenValidPropertySourceWithMasterAndMultipleHostPort() { + + MockPropertySource propertySource = new MockPropertySource(); + propertySource.setProperty("spring.redis.sentinel.master", "myMaster"); + propertySource.setProperty("spring.redis.sentinel.nodes", + StringUtils.collectionToCommaDelimitedString(Arrays.asList(HOST_AND_PORT_1, HOST_AND_PORT_2, HOST_AND_PORT_3))); + + RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource); + + assertThat(config.getSentinels().size(), is(3)); + assertThat(config.getSentinels(), + hasItems(new RedisNode("127.0.0.1", 123), new RedisNode("localhost", 456), new RedisNode("localhost", 789))); + } +}