/* * 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.spi.discovery; 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.MulticastConfig; import com.hazelcast.config.TcpIpConfig; import com.hazelcast.config.XmlConfigBuilder; 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.core.Member; import com.hazelcast.instance.BuildInfoProvider; import com.hazelcast.instance.MemberImpl; import com.hazelcast.instance.Node; import com.hazelcast.instance.TestUtil; import com.hazelcast.logging.ILogger; import com.hazelcast.logging.Logger; import com.hazelcast.nio.Address; import com.hazelcast.partition.membergroup.DefaultMemberGroup; import com.hazelcast.partition.membergroup.MemberGroup; import com.hazelcast.partition.membergroup.MemberGroupFactory; import com.hazelcast.partition.membergroup.SPIAwareMemberGroupFactory; 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.HazelcastParallelClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.TestHazelcastInstanceFactory; import com.hazelcast.test.annotation.QuickTest; import com.hazelcast.version.MemberVersion; 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.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; 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(HazelcastParallelClassRunner.class) @Category(QuickTest.class) public class DiscoverySpiTest extends HazelcastTestSupport { private static final MemberVersion VERSION = MemberVersion.of(BuildInfoProvider.BUILD_INFO.getVersion()); private static final ILogger LOGGER = Logger.getLogger(DiscoverySpiTest.class); @Test public void test_metadata_discovery_on_node_startup() throws Exception { String xmlFileName = "test-hazelcast-discovery-spi-metadata.xml"; InputStream xmlResource = DiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); Config config = new XmlConfigBuilder(xmlResource).build(); TestHazelcastInstanceFactory instanceFactory = createHazelcastInstanceFactory(1); try { HazelcastInstance hazelcastInstance1 = instanceFactory.newHazelcastInstance(config); assertNotNull(hazelcastInstance1); Member localMember = hazelcastInstance1.getCluster().getLocalMember(); assertEquals(Byte.MAX_VALUE, (byte) localMember.getByteAttribute("test-byte")); assertEquals(Short.MAX_VALUE, (short) localMember.getShortAttribute("test-short")); assertEquals(Integer.MAX_VALUE, (int) localMember.getIntAttribute("test-int")); assertEquals(Long.MAX_VALUE, (long) localMember.getLongAttribute("test-long")); assertEquals(Float.MAX_VALUE, localMember.getFloatAttribute("test-float"), 0); assertEquals(Double.MAX_VALUE, localMember.getDoubleAttribute("test-double"), 0); assertTrue(localMember.getBooleanAttribute("test-boolean")); assertEquals("TEST", localMember.getStringAttribute("test-string")); } finally { instanceFactory.shutdownAll(); } } @Test public void testSchema() throws Exception { String xmlFileName = "test-hazelcast-discovery-spi.xml"; SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); URL schemaResource = DiscoverySpiTest.class.getClassLoader().getResource("hazelcast-config-3.9.xsd"); assertNotNull(schemaResource); InputStream xmlResource = DiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); Source source = new StreamSource(xmlResource); Schema schema = factory.newSchema(schemaResource); Validator validator = schema.newValidator(); validator.validate(source); } @Test public void testParsing() throws Exception { String xmlFileName = "test-hazelcast-discovery-spi.xml"; InputStream xmlResource = DiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); Config config = new XmlConfigBuilder(xmlResource).build(); JoinConfig joinConfig = config.getNetworkConfig().getJoin(); AwsConfig awsConfig = joinConfig.getAwsConfig(); assertFalse(awsConfig.isEnabled()); TcpIpConfig tcpIpConfig = joinConfig.getTcpIpConfig(); assertFalse(tcpIpConfig.isEnabled()); MulticastConfig multicastConfig = joinConfig.getMulticastConfig(); assertFalse(multicastConfig.isEnabled()); DiscoveryConfig discoveryConfig = joinConfig.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() { String xmlFileName = "test-hazelcast-discovery-spi.xml"; Config config = getDiscoverySPIConfig(xmlFileName); try { final HazelcastInstance hazelcastInstance1 = Hazelcast.newHazelcastInstance(config); final HazelcastInstance hazelcastInstance2 = Hazelcast.newHazelcastInstance(config); final HazelcastInstance hazelcastInstance3 = Hazelcast.newHazelcastInstance(config); assertNotNull(hazelcastInstance1); assertNotNull(hazelcastInstance2); assertNotNull(hazelcastInstance3); assertClusterSizeEventually(3, hazelcastInstance1, hazelcastInstance2, hazelcastInstance3); } finally { Hazelcast.shutdownAll(); } } @Test public void testNodeFilter_from_xml() throws Exception { String xmlFileName = "test-hazelcast-discovery-spi.xml"; InputStream xmlResource = DiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); Config config = new XmlConfigBuilder(xmlResource).build(); JoinConfig joinConfig = config.getNetworkConfig().getJoin(); DiscoveryConfig discoveryConfig = joinConfig.getDiscoveryConfig(); Address address = new Address("localhost", 5701); DiscoveryServiceSettings settings = buildDiscoveryServiceSettings(address, discoveryConfig, DiscoveryMode.Client); DiscoveryServiceProvider provider = new DefaultDiscoveryServiceProvider(); DiscoveryService discoveryService = provider.newDiscoveryService(settings); 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_AbstractDiscoveryStrategy_getOrNull() throws Exception { PropertyDefinition first = new SimplePropertyDefinition("first", PropertyTypeConverter.STRING); PropertyDefinition second = new SimplePropertyDefinition("second", PropertyTypeConverter.BOOLEAN); PropertyDefinition third = new SimplePropertyDefinition("third", PropertyTypeConverter.INTEGER); PropertyDefinition fourth = new SimplePropertyDefinition("fourth", true, PropertyTypeConverter.STRING); Map<String, Comparable> properties = new HashMap<String, Comparable>(); properties.put("first", "value-first"); properties.put("second", Boolean.FALSE); properties.put("third", 100); // system property > system environment > configuration // property 'first' => "value-first" // property 'second' => true setEnvironment("test.second", "true"); // property 'third' => 300 setEnvironment("test.third", "200"); System.setProperty("test.third", "300"); // property 'fourth' => null PropertyDiscoveryStrategy strategy = new PropertyDiscoveryStrategy(LOGGER, properties); // without lookup of environment assertEquals("value-first", strategy.getOrNull(first)); assertEquals(Boolean.FALSE, strategy.getOrNull(second)); assertEquals(100, ((Integer) strategy.getOrNull(third)).intValue()); assertNull(strategy.getOrNull(fourth)); // with lookup of environment (workaround to set environment doesn't work on all JDKs) if (System.getenv("test.third") != null) { assertEquals("value-first", strategy.getOrNull("test", first)); assertEquals(Boolean.TRUE, strategy.getOrNull("test", second)); assertEquals(300, ((Integer) strategy.getOrNull("test", third)).intValue()); assertNull(strategy.getOrNull("test", fourth)); } } @Test public void test_AbstractDiscoveryStrategy_getOrDefault() throws Exception { PropertyDefinition value = new SimplePropertyDefinition("value", PropertyTypeConverter.INTEGER); Map<String, Comparable> properties = Collections.emptyMap(); PropertyDiscoveryStrategy strategy = new PropertyDiscoveryStrategy(LOGGER, properties); assertEquals(1111, (long) strategy.getOrDefault(value, 1111)); assertEquals(1111, (long) strategy.getOrDefault("test", value, 1111)); } @Test(expected = RuntimeException.class) public void testSPIAwareMemberGroupFactoryInvalidConfig() throws Exception { HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(); try { MemberGroupFactory groupFactory = new SPIAwareMemberGroupFactory(TestUtil.getNode(hazelcastInstance).getDiscoveryService()); Collection<Member> members = createMembers(); groupFactory.createMemberGroups(members); } finally { hazelcastInstance.shutdown(); } } @Test public void testSPIAwareMemberGroupFactoryCreateMemberGroups() throws Exception { String xmlFileName = "test-hazelcast-discovery-spi-metadata.xml"; Config config = getDiscoverySPIConfig(xmlFileName); // we create this instance in order to fully create Node HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); Node node = TestUtil.getNode(hazelcastInstance); assertNotNull(node); MemberGroupFactory groupFactory = new SPIAwareMemberGroupFactory(node.getDiscoveryService()); Collection<Member> members = createMembers(); Collection<MemberGroup> memberGroups = groupFactory.createMemberGroups(members); assertEquals("Member Groups: " + String.valueOf(memberGroups), 2, memberGroups.size()); for (MemberGroup memberGroup : memberGroups) { assertEquals("Member Group: " + String.valueOf(memberGroup), 2, memberGroup.size()); } hazelcastInstance.shutdown(); } @Test(expected = IllegalArgumentException.class) public void test_enabled_whenDiscoveryConfigIsNull() { Config config = new Config(); config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), "true"); config.getNetworkConfig().getJoin().setDiscoveryConfig(null); } @Test public void testCustomDiscoveryService_whenDiscoveredNodes_isNull() { Config config = new Config(); config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), "true"); DiscoveryServiceProvider discoveryServiceProvider = new DiscoveryServiceProvider() { public DiscoveryService newDiscoveryService(DiscoveryServiceSettings arg0) { return mock(DiscoveryService.class); } }; config.getNetworkConfig().getJoin().getDiscoveryConfig().setDiscoveryServiceProvider(discoveryServiceProvider); try { Hazelcast.newHazelcastInstance(config); fail("Instance should not be started!"); } catch (IllegalStateException expected) { } } @Test public void testCustomDiscoveryService_whenDiscoveredNodes_isEmpty() { Config config = new Config(); 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; } }; config.getNetworkConfig().getJoin().getDiscoveryConfig().setDiscoveryServiceProvider(discoveryServiceProvider); HazelcastInstance instance = Hazelcast.newHazelcastInstance(config); try { verify(discoveryService).discoverNodes(); } finally { instance.getLifecycleService().terminate(); } } @SuppressWarnings("unchecked") private static void setEnvironment(String key, String value) throws Exception { Class[] classes = Collections.class.getDeclaredClasses(); Map<String, String> env = System.getenv(); for (Class cl : classes) { if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { Field field = cl.getDeclaredField("m"); field.setAccessible(true); Map<String, String> map = (Map<String, String>) field.get(env); map.put(key, value); } } } private DiscoveryServiceSettings buildDiscoveryServiceSettings(Address address, DiscoveryConfig config, DiscoveryMode mode) { return new DiscoveryServiceSettings().setConfigClassLoader(DiscoverySpiTest.class.getClassLoader()) .setDiscoveryConfig(config).setDiscoveryMode(mode).setLogger(LOGGER) .setDiscoveryNode(new SimpleDiscoveryNode(address)); } private static class PropertyDiscoveryStrategy extends AbstractDiscoveryStrategy { PropertyDiscoveryStrategy(ILogger logger, Map<String, Comparable> properties) { super(logger, properties); } @Override public Iterable<DiscoveryNode> discoverNodes() { return null; } @Override public <T extends Comparable> T getOrNull(PropertyDefinition property) { return super.getOrNull(property); } @Override public <T extends Comparable> T getOrNull(String prefix, PropertyDefinition property) { return super.getOrNull(prefix, property); } @Override public <T extends Comparable> T getOrDefault(PropertyDefinition property, T defaultValue) { return super.getOrDefault(property, defaultValue); } @Override public <T extends Comparable> T getOrDefault(String prefix, PropertyDefinition property, T defaultValue) { return super.getOrDefault(prefix, property, defaultValue); } } private static class TestDiscoveryStrategy implements DiscoveryStrategy { @Override public void start() { } @Override public Collection<DiscoveryNode> discoverNodes() { try { List<DiscoveryNode> discoveryNodes = new ArrayList<DiscoveryNode>(4); Address address = new Address("127.0.0.1", 50001); discoveryNodes.add(new SimpleDiscoveryNode(address)); address = new Address("127.0.0.1", 50002); discoveryNodes.add(new SimpleDiscoveryNode(address)); address = new Address("127.0.0.1", 50003); discoveryNodes.add(new SimpleDiscoveryNode(address)); address = new Address("127.0.0.1", 50004); discoveryNodes.add(new SimpleDiscoveryNode(address)); 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 node, ILogger logger, Map<String, Comparable> properties) { return new CollectingDiscoveryStrategy(node, 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; 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(); discoveryNodes.add(discoveryNode); getLogger(); getProperties(); } // need to provide a custom impl @Override public PartitionGroupStrategy getPartitionGroupStrategy() { return new SPIPartitionGroupStrategy(); } @Override public Iterable<DiscoveryNode> discoverNodes() { return new ArrayList<DiscoveryNode>(discoveryNodes); } @Override public void destroy() { super.destroy(); 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; } } public static class MetadataProvidingDiscoveryStrategyFactory implements DiscoveryStrategyFactory { @Override public Class<? extends DiscoveryStrategy> getDiscoveryStrategyType() { return MetadataProvidingDiscoveryStrategy.class; } @Override public DiscoveryStrategy newDiscoveryStrategy(DiscoveryNode discoveryNode, ILogger logger, Map<String, Comparable> properties) { return new MetadataProvidingDiscoveryStrategy(discoveryNode, logger, properties); } @Override public Collection<PropertyDefinition> getConfigurationProperties() { return Collections.emptyList(); } } private static class MetadataProvidingDiscoveryStrategy extends AbstractDiscoveryStrategy { private final DiscoveryNode discoveryNode; MetadataProvidingDiscoveryStrategy(DiscoveryNode discoveryNode, ILogger logger, Map<String, Comparable> properties) { super(logger, properties); this.discoveryNode = discoveryNode; } @Override public Iterable<DiscoveryNode> discoverNodes() { return Collections.singleton(discoveryNode); } @Override public PartitionGroupStrategy getPartitionGroupStrategy() { return new SPIPartitionGroupStrategy(); } @Override public Map<String, Object> discoverLocalMetadata() { Map<String, Object> metadata = new HashMap<String, Object>(); metadata.put("test-byte", Byte.MAX_VALUE); metadata.put("test-short", Short.MAX_VALUE); metadata.put("test-int", Integer.MAX_VALUE); metadata.put("test-long", Long.MAX_VALUE); metadata.put("test-float", Float.MAX_VALUE); metadata.put("test-double", Double.MAX_VALUE); metadata.put("test-boolean", Boolean.TRUE); metadata.put("test-string", "TEST"); return metadata; } } private static class SPIPartitionGroupStrategy implements PartitionGroupStrategy { @Override public Iterable<MemberGroup> getMemberGroups() { List<MemberGroup> groups = new ArrayList<MemberGroup>(); try { groups.add(new DefaultMemberGroup(createMembers())); groups.add(new DefaultMemberGroup(createMembers())); } catch (UnknownHostException e) { e.printStackTrace(); } return groups; } } private static Collection<Member> createMembers() throws UnknownHostException { Collection<Member> members = new HashSet<Member>(); InetAddress fakeAddress = InetAddress.getLocalHost(); members.add(new MemberImpl(new Address("192.192.0.1", fakeAddress, 5701), VERSION, true)); members.add(new MemberImpl(new Address("192.192.0.1", fakeAddress, 5702), VERSION, false)); members.add(new MemberImpl(new Address("download.hazelcast.org", fakeAddress, 5701), VERSION, false)); members.add(new MemberImpl(new Address("download.hazelcast.org", fakeAddress, 5702), VERSION, false)); return members; } private static Config getDiscoverySPIConfig(String xmlFileName) { InputStream xmlResource = DiscoverySpiTest.class.getClassLoader().getResourceAsStream(xmlFileName); Config config = new XmlConfigBuilder(xmlResource).build(); 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); DiscoveryConfig discoveryConfig = config.getNetworkConfig().getJoin().getDiscoveryConfig(); discoveryConfig.getDiscoveryStrategyConfigs().clear(); DiscoveryStrategyConfig strategyConfig = new DiscoveryStrategyConfig(factory, Collections.<String, Comparable>emptyMap()); discoveryConfig.addDiscoveryStrategyConfig(strategyConfig); return config; } }