/* * 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.test; import com.hazelcast.config.Config; import com.hazelcast.config.XmlConfigBuilder; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.instance.HazelcastInstanceFactory; import com.hazelcast.instance.NodeContext; import com.hazelcast.nio.Address; import com.hazelcast.spi.properties.GroupProperty; import com.hazelcast.test.mocknetwork.TestNodeRegistry; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import static com.hazelcast.instance.TestUtil.getNode; import static com.hazelcast.instance.TestUtil.terminateInstance; import static com.hazelcast.test.HazelcastTestSupport.getAddress; import static com.hazelcast.util.Preconditions.checkNotNull; import static java.util.Collections.EMPTY_SET; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableCollection; public class TestHazelcastInstanceFactory { protected final TestNodeRegistry registry; private static final AtomicInteger PORTS = new AtomicInteger(5000); private final ConcurrentMap<Integer, Address> addressMap = new ConcurrentHashMap<Integer, Address>(); private final boolean mockNetwork = TestEnvironment.isMockNetwork(); private final AtomicInteger nodeIndex = new AtomicInteger(); private final int count; public TestHazelcastInstanceFactory() { this(0); } public TestHazelcastInstanceFactory(int count) { this.count = count; this.registry = getRegistry(createAddresses(PORTS, count)); } public TestHazelcastInstanceFactory(String... addresses) { this(-1, addresses); } public TestHazelcastInstanceFactory(int initialPort, String... addresses) { this.count = addresses.length; this.registry = getRegistry(createAddresses(initialPort, PORTS, addresses)); } public TestHazelcastInstanceFactory(Collection<Address> addresses) { this.count = addresses.size(); this.registry = getRegistry(addresses); } private TestNodeRegistry getRegistry(Collection<Address> addresses) { if (!mockNetwork) { return null; } int ix = 0; for (Address address : addresses) { addressMap.put(ix++, address); } return new TestNodeRegistry(getKnownAddresses()); } /** * Delegates to {@link #newHazelcastInstance(Config) {@code newHazelcastInstance(null)}}. */ public HazelcastInstance newHazelcastInstance() { return newHazelcastInstance((Config) null); } /** * Creates a new test Hazelcast instance. * * @param config the config to use; use {@code null} to get the default config */ public HazelcastInstance newHazelcastInstance(Config config) { String instanceName = config != null ? config.getInstanceName() : null; if (mockNetwork) { config = initOrCreateConfig(config); NodeContext nodeContext = registry.createNodeContext(nextAddress()); return HazelcastInstanceFactory.newHazelcastInstance(config, instanceName, nodeContext); } return HazelcastInstanceFactory.newHazelcastInstance(config); } /** * Creates a new test Hazelcast instance. * * @param address the address to use as Member's address instead of picking the next address */ public HazelcastInstance newHazelcastInstance(Address address) { return newHazelcastInstance(address, null); } /** * Creates a new test Hazelcast instance. * * @param address the address to use as Member's address instead of picking the next address * @param config the config to use; use {@code null} to get the default config */ public HazelcastInstance newHazelcastInstance(Address address, Config config) { final String instanceName = config != null ? config.getInstanceName() : null; if (mockNetwork) { config = initOrCreateConfig(config); NodeContext nodeContext = registry.createNodeContext(address); return HazelcastInstanceFactory.newHazelcastInstance(config, instanceName, nodeContext); } throw new UnsupportedOperationException("Explicit address is only available for mock network setup!"); } /** * Creates a new test Hazelcast instance which is only allowed to connect to specified addresses: * <ul> * <li>{@code blockedAddresses} are blacklisted in its {@code MockJoiner}</li> * <li>connections to {@code blockedAddresses} are blocked by its {@code FirewallingConnectionManager}</li> * </ul> * This is handy in split-brain tests, when a new instance should be started on a specific network partition * of the split brain. * * @param config the config to use; use {@code null} to get the default config * @param blockedAddresses addresses to which the new instance is allowed to communicate */ public HazelcastInstance newHazelcastInstance(Config config, Address[] blockedAddresses) { return newHazelcastInstance(null, config, blockedAddresses); } /** * Creates a new test Hazelcast instance which is only allowed to connect to specified addresses: * <ul> * <li>{@code blockedAddresses} are blacklisted in its {@code MockJoiner}</li> * <li>connections to {@code blockedAddresses} are blocked by its {@code FirewallingConnectionManager}</li> * </ul> * This is handy in split-brain tests, when a new instance should be started on a specific network partition * of the split brain. * * @param address the address to use as Member's address; if {@code null}, then uses the next address * @param config the config to use; use {@code null} to get the default config * @param blockedAddresses addresses to which the new instance is allowed to communicate */ public HazelcastInstance newHazelcastInstance(Address address, Config config, Address[] blockedAddresses) { final String instanceName = config != null ? config.getInstanceName() : null; final Address thisAddress = address != null ? address : nextAddress(); if (mockNetwork) { config = initOrCreateConfig(config); NodeContext nodeContext = registry.createNodeContext(thisAddress, blockedAddresses == null ? EMPTY_SET : new HashSet<Address>(Arrays.asList(blockedAddresses))); return HazelcastInstanceFactory.newHazelcastInstance(config, instanceName, nodeContext); } throw new UnsupportedOperationException("Explicit address is only available for mock network setup!"); } public Address nextAddress() { int id = nodeIndex.getAndIncrement(); Address currentAddress = addressMap.get(id); if (currentAddress != null) { return currentAddress; } Address newAddress = createAddress("127.0.0.1", PORTS.incrementAndGet()); addressMap.put(id, newAddress); return newAddress; } public HazelcastInstance[] newInstances() { return newInstances(new Config()); } public HazelcastInstance[] newInstances(Config config, int nodeCount) { HazelcastInstance[] instances = new HazelcastInstance[nodeCount]; for (int i = 0; i < nodeCount; i++) { instances[i] = newHazelcastInstance(config); } return instances; } public HazelcastInstance[] newInstances(Config config) { return newInstances(config, count); } public Collection<HazelcastInstance> getAllHazelcastInstances() { if (mockNetwork) { return registry.getAllHazelcastInstances(); } return Hazelcast.getAllHazelcastInstances(); } public Collection<Address> getKnownAddresses() { return unmodifiableCollection(addressMap.values()); } public TestNodeRegistry getRegistry() { return registry; } public HazelcastInstance getInstance(Address address) { if (mockNetwork) { return registry.getInstance(address); } Set<HazelcastInstance> instances = HazelcastInstanceFactory.getAllHazelcastInstances(); for (HazelcastInstance instance : instances) { if (address.equals(getAddress(instance))) { return instance; } } return null; } /** * Terminates supplied instance by releasing internal resources. * * @param instance the instance to terminate */ public void terminate(HazelcastInstance instance) { Address address = getNode(instance).address; terminateInstance(instance); registry.removeInstance(address); } public void shutdownAll() { if (mockNetwork) { registry.shutdown(); addressMap.clear(); } else { Hazelcast.shutdownAll(); } } public void terminateAll() { if (mockNetwork) { registry.terminate(); } else { HazelcastInstanceFactory.terminateAll(); } } @Override public String toString() { return "TestHazelcastInstanceFactory{addresses=" + addressMap.values() + '}'; } private List<Address> createAddresses(AtomicInteger ports, int count) { if (!mockNetwork || count == 0) { return emptyList(); } List<Address> addresses = new ArrayList<Address>(count); for (int i = 0; i < count; i++) { addresses.add(createAddress("127.0.0.1", ports.incrementAndGet())); } return addresses; } private List<Address> createAddresses(int initialPort, AtomicInteger ports, String... addressArray) { if (!mockNetwork) { return emptyList(); } checkElementsNotNull(addressArray); int count = addressArray.length; List<Address> addresses = new ArrayList<Address>(count); for (String address : addressArray) { int port = initialPort == -1 ? ports.incrementAndGet() : initialPort++; addresses.add(createAddress(address, port)); } return addresses; } private static Address createAddress(String host, int port) { try { return new Address(host, port); } catch (UnknownHostException e) { e.printStackTrace(); } return null; } private static <T> void checkElementsNotNull(T[] array) { checkNotNull(array, "Array should not be null"); for (Object element : array) { checkNotNull(element, "Array element should not be null"); } } private static Config initOrCreateConfig(Config config) { if (config == null) { config = new XmlConfigBuilder().build(); } config.setProperty(GroupProperty.WAIT_SECONDS_BEFORE_JOIN.getName(), "0"); config.setProperty(GroupProperty.GRACEFUL_SHUTDOWN_MAX_WAIT.getName(), "120"); config.setProperty(GroupProperty.PARTITION_BACKUP_SYNC_INTERVAL.getName(), "1"); config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false); return config; } public int getCount() { return count; } }