/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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.hazelcast.client.spi.impl.discovery; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; import com.hazelcast.client.config.ClientNetworkConfig; import com.hazelcast.client.config.XmlClientConfigBuilder; import com.hazelcast.client.connection.AddressTranslator; import com.hazelcast.config.AwsConfig; import com.hazelcast.config.Config; import com.hazelcast.config.DiscoveryConfig; import com.hazelcast.config.DiscoveryStrategyConfig; import com.hazelcast.config.InterfacesConfig; import com.hazelcast.config.JoinConfig; import com.hazelcast.config.properties.PropertyDefinition; import com.hazelcast.config.properties.PropertyTypeConverter; import com.hazelcast.config.properties.SimplePropertyDefinition; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.logging.ILogger; import com.hazelcast.logging.Logger; import com.hazelcast.nio.Address; import com.hazelcast.spi.discovery.AbstractDiscoveryStrategy; import com.hazelcast.spi.discovery.DiscoveryNode; import com.hazelcast.spi.discovery.DiscoveryStrategy; import com.hazelcast.spi.discovery.DiscoveryStrategyFactory; import com.hazelcast.spi.discovery.NodeFilter; import com.hazelcast.spi.discovery.SimpleDiscoveryNode; import com.hazelcast.spi.discovery.impl.DefaultDiscoveryService; import com.hazelcast.spi.discovery.impl.DefaultDiscoveryServiceProvider; import com.hazelcast.spi.discovery.integration.DiscoveryMode; import com.hazelcast.spi.discovery.integration.DiscoveryService; import com.hazelcast.spi.discovery.integration.DiscoveryServiceProvider; import com.hazelcast.spi.discovery.integration.DiscoveryServiceSettings; import com.hazelcast.spi.partitiongroup.PartitionGroupStrategy; import com.hazelcast.spi.properties.GroupProperty; import com.hazelcast.test.HazelcastSerialClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.annotation.QuickTest; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import javax.xml.XMLConstants; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import java.io.InputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(HazelcastSerialClassRunner.class) @Category(QuickTest.class) public class ClientDiscoverySpiTest extends HazelcastTestSupport { private static final ILogger LOGGER = Logger.getLogger(ClientDiscoverySpiTest.class); @Test public void testSchema() throws Exception { String xmlFileName = "hazelcast-client-discovery-spi-test.xml"; SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); URL schemaResource = ClientDiscoverySpiTest.class.getClassLoader().getResource("hazelcast-client-config-3.9.xsd"); Schema schema = factory.newSchema(schemaResource); InputStream xmlResource = ClientDiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); Source source = new StreamSource(xmlResource); Validator validator = schema.newValidator(); validator.validate(source); } @Test public void testParsing() throws Exception { String xmlFileName = "hazelcast-client-discovery-spi-test.xml"; InputStream xmlResource = ClientDiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); ClientConfig clientConfig = new XmlClientConfigBuilder(xmlResource).build(); ClientNetworkConfig networkConfig = clientConfig.getNetworkConfig(); AwsConfig awsConfig = networkConfig.getAwsConfig(); assertNull(awsConfig); DiscoveryConfig discoveryConfig = networkConfig.getDiscoveryConfig(); assertTrue(discoveryConfig.isEnabled()); assertEquals(1, discoveryConfig.getDiscoveryStrategyConfigs().size()); DiscoveryStrategyConfig providerConfig = discoveryConfig.getDiscoveryStrategyConfigs().iterator().next(); assertEquals(3, providerConfig.getProperties().size()); assertEquals("foo", providerConfig.getProperties().get("key-string")); assertEquals("123", providerConfig.getProperties().get("key-int")); assertEquals("true", providerConfig.getProperties().get("key-boolean")); } @Test public void testNodeStartup() { Config config = new Config(); config.setProperty("hazelcast.discovery.enabled", "true"); config.getNetworkConfig().setPort(50001); InterfacesConfig interfaces = config.getNetworkConfig().getInterfaces(); interfaces.clear(); interfaces.setEnabled(true); interfaces.addInterface("127.0.0.1"); List<DiscoveryNode> discoveryNodes = new CopyOnWriteArrayList<DiscoveryNode>(); DiscoveryStrategyFactory factory = new CollectingDiscoveryStrategyFactory(discoveryNodes); JoinConfig join = config.getNetworkConfig().getJoin(); join.getTcpIpConfig().setEnabled(false); join.getMulticastConfig().setEnabled(false); DiscoveryConfig discoveryConfig = join.getDiscoveryConfig(); discoveryConfig.getDiscoveryStrategyConfigs().clear(); DiscoveryStrategyConfig strategyConfig = new DiscoveryStrategyConfig(factory, Collections.<String, Comparable>emptyMap()); discoveryConfig.addDiscoveryStrategyConfig(strategyConfig); final HazelcastInstance hazelcastInstance1 = Hazelcast.newHazelcastInstance(config); final HazelcastInstance hazelcastInstance2 = Hazelcast.newHazelcastInstance(config); final HazelcastInstance hazelcastInstance3 = Hazelcast.newHazelcastInstance(config); try { ClientConfig clientConfig = new ClientConfig(); clientConfig.setProperty("hazelcast.discovery.enabled", "true"); discoveryConfig = clientConfig.getNetworkConfig().getDiscoveryConfig(); discoveryConfig.getDiscoveryStrategyConfigs().clear(); strategyConfig = new DiscoveryStrategyConfig(factory, Collections.<String, Comparable>emptyMap()); discoveryConfig.addDiscoveryStrategyConfig(strategyConfig); final HazelcastInstance client = HazelcastClient.newHazelcastClient(clientConfig); assertNotNull(hazelcastInstance1); assertNotNull(hazelcastInstance2); assertNotNull(hazelcastInstance3); assertNotNull(client); assertClusterSizeEventually(3, hazelcastInstance1, hazelcastInstance2, hazelcastInstance3, client); } finally { HazelcastClient.shutdownAll(); Hazelcast.shutdownAll(); } } @Test public void testDiscoveryServiceLifecycleMethodsCalledWhenClientAndServerStartAndShutdown() { //Given Config config = new Config(); config.setProperty("hazelcast.discovery.enabled", "true"); config.getNetworkConfig().setPort(50001); InterfacesConfig interfaces = config.getNetworkConfig().getInterfaces(); interfaces.clear(); interfaces.setEnabled(true); interfaces.addInterface("127.0.0.1"); //Both server and client are using the same LifecycleDiscoveryStrategyFactory so latch count is set to 2. CountDownLatch startLatch = new CountDownLatch(2); CountDownLatch stopLatch = new CountDownLatch(2); List<DiscoveryNode> discoveryNodes = new CopyOnWriteArrayList<DiscoveryNode>(); DiscoveryStrategyFactory factory = new LifecycleDiscoveryStrategyFactory(startLatch, stopLatch, discoveryNodes); JoinConfig join = config.getNetworkConfig().getJoin(); join.getTcpIpConfig().setEnabled(false); join.getMulticastConfig().setEnabled(false); DiscoveryConfig discoveryConfig = join.getDiscoveryConfig(); discoveryConfig.getDiscoveryStrategyConfigs().clear(); DiscoveryStrategyConfig strategyConfig = new DiscoveryStrategyConfig(factory, Collections.<String, Comparable>emptyMap()); discoveryConfig.addDiscoveryStrategyConfig(strategyConfig); final HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); ClientConfig clientConfig = new ClientConfig(); clientConfig.setProperty("hazelcast.discovery.enabled", "true"); discoveryConfig = clientConfig.getNetworkConfig().getDiscoveryConfig(); discoveryConfig.getDiscoveryStrategyConfigs().clear(); strategyConfig = new DiscoveryStrategyConfig(factory, Collections.<String, Comparable>emptyMap()); discoveryConfig.addDiscoveryStrategyConfig(strategyConfig); final HazelcastInstance client = HazelcastClient.newHazelcastClient(clientConfig); assertNotNull(hazelcastInstance); assertNotNull(client); //When HazelcastClient.shutdownAll(); Hazelcast.shutdownAll(); //Then assertOpenEventually(startLatch); assertOpenEventually(stopLatch); } @Test public void testNodeFilter_from_xml() throws Exception { String xmlFileName = "hazelcast-client-discovery-spi-test.xml"; InputStream xmlResource = ClientDiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); ClientConfig clientConfig = new XmlClientConfigBuilder(xmlResource).build(); ClientNetworkConfig networkConfig = clientConfig.getNetworkConfig(); DiscoveryConfig discoveryConfig = networkConfig.getDiscoveryConfig(); DiscoveryServiceProvider provider = new DefaultDiscoveryServiceProvider(); DiscoveryService discoveryService = provider.newDiscoveryService(buildDiscoveryServiceSettings(discoveryConfig)); discoveryService.start(); discoveryService.discoverNodes(); discoveryService.destroy(); Field nodeFilterField = DefaultDiscoveryService.class.getDeclaredField("nodeFilter"); nodeFilterField.setAccessible(true); TestNodeFilter nodeFilter = (TestNodeFilter) nodeFilterField.get(discoveryService); assertEquals(4, nodeFilter.getNodes().size()); } @Test public void test_discovery_address_translator() throws Exception { String xmlFileName = "hazelcast-client-discovery-spi-test.xml"; InputStream xmlResource = ClientDiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); ClientConfig clientConfig = new XmlClientConfigBuilder(xmlResource).build(); ClientNetworkConfig networkConfig = clientConfig.getNetworkConfig(); DiscoveryConfig discoveryConfig = networkConfig.getDiscoveryConfig(); DiscoveryServiceProvider provider = new DefaultDiscoveryServiceProvider(); DiscoveryService discoveryService = provider.newDiscoveryService(buildDiscoveryServiceSettings(discoveryConfig)); AddressTranslator translator = new DiscoveryAddressTranslator(discoveryService, false); Address address = new Address("127.0.0.1", 50001); assertNull(translator.translate(null)); assertEquals(address, translator.translate(address)); // Enforce refresh of the internal mapping assertEquals(address, translator.translate(address)); } @Test public void test_discovery_address_translator_with_public_ip() throws Exception { String xmlFileName = "hazelcast-client-discovery-spi-test.xml"; InputStream xmlResource = ClientDiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); ClientConfig clientConfig = new XmlClientConfigBuilder(xmlResource).build(); ClientNetworkConfig networkConfig = clientConfig.getNetworkConfig(); DiscoveryConfig discoveryConfig = networkConfig.getDiscoveryConfig(); DiscoveryServiceProvider provider = new DefaultDiscoveryServiceProvider(); DiscoveryService discoveryService = provider.newDiscoveryService(buildDiscoveryServiceSettings(discoveryConfig)); AddressTranslator translator = new DiscoveryAddressTranslator(discoveryService, true); Address publicAddress = new Address("127.0.0.1", 50001); Address privateAddress = new Address("127.0.0.1", 1); // Enforce refresh of the internal mapping assertEquals(publicAddress, translator.translate(privateAddress)); } @Test(expected = IllegalArgumentException.class) public void test_enabled_whenDiscoveryConfigIsNull() { ClientConfig config = new ClientConfig(); config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), "true"); ClientNetworkConfig networkConfig = config.getNetworkConfig(); networkConfig.setDiscoveryConfig(null); } @Test public void test_enabled_whenDiscoveryConfigIsEmpty() { ClientConfig config = new ClientConfig(); config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), "true"); ClientNetworkConfig networkConfig = config.getNetworkConfig(); networkConfig.setConnectionAttemptLimit(1); networkConfig.setConnectionAttemptPeriod(1); try { HazelcastClient.newHazelcastClient(config); } catch (IllegalStateException expected) { // no server available } } @Test public void test_CustomDiscoveryService_whenDiscoveredNodes_isNull() { ClientConfig config = new ClientConfig(); config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), "true"); final DiscoveryService discoveryService = mock(DiscoveryService.class); DiscoveryServiceProvider discoveryServiceProvider = new DiscoveryServiceProvider() { public DiscoveryService newDiscoveryService(DiscoveryServiceSettings arg0) { return discoveryService; } }; ClientNetworkConfig networkConfig = config.getNetworkConfig(); networkConfig.setConnectionAttemptLimit(1); networkConfig.setConnectionAttemptPeriod(1); networkConfig.getDiscoveryConfig().setDiscoveryServiceProvider(discoveryServiceProvider); try { HazelcastClient.newHazelcastClient(config); fail("Client cannot start, discovery nodes is null!"); } catch (NullPointerException expected) { // discovered nodes is null } verify(discoveryService).discoverNodes(); } @Test public void test_CustomDiscoveryService_whenDiscoveredNodes_isEmpty() { ClientConfig config = new ClientConfig(); config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), "true"); final DiscoveryService discoveryService = mock(DiscoveryService.class); DiscoveryServiceProvider discoveryServiceProvider = new DiscoveryServiceProvider() { public DiscoveryService newDiscoveryService(DiscoveryServiceSettings arg0) { when(discoveryService.discoverNodes()).thenReturn(Collections.<DiscoveryNode>emptyList()); return discoveryService; } }; ClientNetworkConfig networkConfig = config.getNetworkConfig(); networkConfig.setConnectionAttemptLimit(1); networkConfig.setConnectionAttemptPeriod(1); networkConfig.getDiscoveryConfig().setDiscoveryServiceProvider(discoveryServiceProvider); try { HazelcastClient.newHazelcastClient(config); } catch (IllegalStateException expected) { // no server available } verify(discoveryService).discoverNodes(); } private DiscoveryServiceSettings buildDiscoveryServiceSettings(DiscoveryConfig config) { return new DiscoveryServiceSettings().setConfigClassLoader(ClientDiscoverySpiTest.class.getClassLoader()) .setDiscoveryConfig(config).setDiscoveryMode(DiscoveryMode.Client).setLogger(LOGGER); } private static class TestDiscoveryStrategy implements DiscoveryStrategy { @Override public void start() { } @Override public Collection<DiscoveryNode> discoverNodes() { try { List<DiscoveryNode> discoveryNodes = new ArrayList<DiscoveryNode>(4); Address privateAddress = new Address("127.0.0.1", 1); Address publicAddress = new Address("127.0.0.1", 50001); discoveryNodes.add(new SimpleDiscoveryNode(privateAddress, publicAddress)); privateAddress = new Address("127.0.0.1", 2); publicAddress = new Address("127.0.0.1", 50002); discoveryNodes.add(new SimpleDiscoveryNode(privateAddress, publicAddress)); privateAddress = new Address("127.0.0.1", 3); publicAddress = new Address("127.0.0.1", 50003); discoveryNodes.add(new SimpleDiscoveryNode(privateAddress, publicAddress)); privateAddress = new Address("127.0.0.1", 4); publicAddress = new Address("127.0.0.1", 50004); discoveryNodes.add(new SimpleDiscoveryNode(privateAddress, publicAddress)); return discoveryNodes; } catch (Exception e) { throw new RuntimeException(e); } } @Override public void destroy() { } @Override public PartitionGroupStrategy getPartitionGroupStrategy() { return null; } @Override public Map<String, Object> discoverLocalMetadata() { return Collections.emptyMap(); } } public static class TestDiscoveryStrategyFactory implements DiscoveryStrategyFactory { private final Collection<PropertyDefinition> propertyDefinitions; public TestDiscoveryStrategyFactory() { List<PropertyDefinition> propertyDefinitions = new ArrayList<PropertyDefinition>(); propertyDefinitions.add(new SimplePropertyDefinition("key-string", PropertyTypeConverter.STRING)); propertyDefinitions.add(new SimplePropertyDefinition("key-int", PropertyTypeConverter.INTEGER)); propertyDefinitions.add(new SimplePropertyDefinition("key-boolean", PropertyTypeConverter.BOOLEAN)); propertyDefinitions.add(new SimplePropertyDefinition("key-something", true, PropertyTypeConverter.STRING)); this.propertyDefinitions = Collections.unmodifiableCollection(propertyDefinitions); } @Override public Class<? extends DiscoveryStrategy> getDiscoveryStrategyType() { return TestDiscoveryStrategy.class; } @Override public DiscoveryStrategy newDiscoveryStrategy(DiscoveryNode discoveryNode, ILogger logger, Map<String, Comparable> properties) { return new TestDiscoveryStrategy(); } @Override public Collection<PropertyDefinition> getConfigurationProperties() { return propertyDefinitions; } } public static class CollectingDiscoveryStrategyFactory implements DiscoveryStrategyFactory { private final List<DiscoveryNode> discoveryNodes; private CollectingDiscoveryStrategyFactory(List<DiscoveryNode> discoveryNodes) { this.discoveryNodes = discoveryNodes; } @Override public Class<? extends DiscoveryStrategy> getDiscoveryStrategyType() { return CollectingDiscoveryStrategy.class; } @Override public DiscoveryStrategy newDiscoveryStrategy(DiscoveryNode discoveryNode, ILogger logger, Map<String, Comparable> properties) { return new CollectingDiscoveryStrategy(discoveryNode, discoveryNodes, logger, properties); } @Override public Collection<PropertyDefinition> getConfigurationProperties() { return null; } } private static class CollectingDiscoveryStrategy extends AbstractDiscoveryStrategy { private final List<DiscoveryNode> discoveryNodes; private final DiscoveryNode discoveryNode; public CollectingDiscoveryStrategy(DiscoveryNode discoveryNode, List<DiscoveryNode> discoveryNodes, ILogger logger, Map<String, Comparable> properties) { super(logger, properties); this.discoveryNodes = discoveryNodes; this.discoveryNode = discoveryNode; } @Override public void start() { super.start(); if (discoveryNode != null) { discoveryNodes.add(discoveryNode); } getLogger(); getProperties(); } @Override public Iterable<DiscoveryNode> discoverNodes() { return new ArrayList<DiscoveryNode>(discoveryNodes); } @Override public void destroy() { super.destroy(); discoveryNodes.remove(discoveryNode); } } public static class LifecycleDiscoveryStrategyFactory implements DiscoveryStrategyFactory { private final CountDownLatch startLatch; private final CountDownLatch stopLatch; private final List<DiscoveryNode> discoveryNodes; private LifecycleDiscoveryStrategyFactory(CountDownLatch startLatch, CountDownLatch stopLatch, List<DiscoveryNode> discoveryNodes) { this.startLatch = startLatch; this.stopLatch = stopLatch; this.discoveryNodes = discoveryNodes; } @Override public Class<? extends DiscoveryStrategy> getDiscoveryStrategyType() { return LifecycleDiscoveryStrategy.class; } @Override public DiscoveryStrategy newDiscoveryStrategy(DiscoveryNode discoveryNode, ILogger logger, Map<String, Comparable> properties) { return new LifecycleDiscoveryStrategy(startLatch, stopLatch, discoveryNode, discoveryNodes, logger, properties); } @Override public Collection<PropertyDefinition> getConfigurationProperties() { return null; } } private static class LifecycleDiscoveryStrategy extends AbstractDiscoveryStrategy { private final CountDownLatch startLatch; private final CountDownLatch stopLatch; private final List<DiscoveryNode> discoveryNodes; private final DiscoveryNode discoveryNode; public LifecycleDiscoveryStrategy(CountDownLatch startLatch, CountDownLatch stopLatch, DiscoveryNode discoveryNode, List<DiscoveryNode> discoveryNodes, ILogger logger, Map<String, Comparable> properties) { super(logger, properties); this.startLatch = startLatch; this.stopLatch = stopLatch; this.discoveryNodes = discoveryNodes; this.discoveryNode = discoveryNode; } @Override public void start() { super.start(); startLatch.countDown(); if (discoveryNode != null) { discoveryNodes.add(discoveryNode); } } @Override public Iterable<DiscoveryNode> discoverNodes() { return new ArrayList<DiscoveryNode>(discoveryNodes); } @Override public void destroy() { super.destroy(); stopLatch.countDown(); discoveryNodes.remove(discoveryNode); } } public static class TestNodeFilter implements NodeFilter { private final List<DiscoveryNode> nodes = new ArrayList<DiscoveryNode>(); @Override public boolean test(DiscoveryNode candidate) { nodes.add(candidate); return true; } private List<DiscoveryNode> getNodes() { return nodes; } } }