/* * 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.test; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientAwsConfig; import com.hazelcast.client.config.ClientConfig; import com.hazelcast.client.connection.AddressTranslator; import com.hazelcast.client.connection.ClientConnectionManager; import com.hazelcast.client.connection.nio.ClientConnection; import com.hazelcast.client.connection.nio.ClientConnectionManagerImpl; import com.hazelcast.client.impl.ClientConnectionManagerFactory; import com.hazelcast.client.impl.HazelcastClientInstanceImpl; import com.hazelcast.client.impl.protocol.ClientMessage; import com.hazelcast.client.spi.impl.AwsAddressTranslator; import com.hazelcast.client.spi.impl.DefaultAddressTranslator; import com.hazelcast.client.spi.impl.discovery.DiscoveryAddressTranslator; import com.hazelcast.client.spi.properties.ClientProperty; import com.hazelcast.client.test.TwoWayBlockableExecutor.LockPair; import com.hazelcast.core.HazelcastException; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.instance.Node; import com.hazelcast.instance.NodeState; import com.hazelcast.instance.TestUtil; import com.hazelcast.internal.networking.OutboundFrame; import com.hazelcast.logging.ILogger; import com.hazelcast.logging.Logger; import com.hazelcast.nio.Address; import com.hazelcast.nio.ConnectionType; import com.hazelcast.spi.discovery.integration.DiscoveryService; import com.hazelcast.spi.exception.TargetDisconnectedException; import com.hazelcast.spi.impl.NodeEngineImpl; import com.hazelcast.test.mocknetwork.MockConnection; import com.hazelcast.test.mocknetwork.TestNodeRegistry; import com.hazelcast.util.ConcurrencyUtil; import com.hazelcast.util.ConstructorFunction; import com.hazelcast.util.ExceptionUtil; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantReadWriteLock; class TestClientRegistry { private static final ILogger LOGGER = Logger.getLogger(HazelcastClient.class); private final TestNodeRegistry nodeRegistry; TestClientRegistry(TestNodeRegistry nodeRegistry) { this.nodeRegistry = nodeRegistry; } ClientConnectionManagerFactory createClientServiceFactory(String host, AtomicInteger ports) { return new MockClientConnectionManagerFactory(host, ports); } private class MockClientConnectionManagerFactory implements ClientConnectionManagerFactory { private final String host; private final AtomicInteger ports; MockClientConnectionManagerFactory(String host, AtomicInteger ports) { this.host = host; this.ports = ports; } @Override public ClientConnectionManager createConnectionManager(ClientConfig config, HazelcastClientInstanceImpl client, DiscoveryService discoveryService) { final ClientAwsConfig awsConfig = config.getNetworkConfig().getAwsConfig(); AddressTranslator addressTranslator; if (awsConfig != null && awsConfig.isEnabled()) { try { addressTranslator = new AwsAddressTranslator(awsConfig, client.getLoggingService()); } catch (NoClassDefFoundError e) { LOGGER.warning("hazelcast-aws.jar might be missing!"); throw e; } } else if (discoveryService != null) { addressTranslator = new DiscoveryAddressTranslator(discoveryService, client.getProperties().getBoolean(ClientProperty.DISCOVERY_SPI_PUBLIC_IP_ENABLED)); } else { addressTranslator = new DefaultAddressTranslator(); } return new MockClientConnectionManager(client, addressTranslator, host, ports); } } class MockClientConnectionManager extends ClientConnectionManagerImpl { private final String host; private final AtomicInteger ports; private final HazelcastClientInstanceImpl client; private final ConcurrentHashMap<Address, LockPair> addressBlockMap = new ConcurrentHashMap<Address, LockPair>(); MockClientConnectionManager(HazelcastClientInstanceImpl client, AddressTranslator addressTranslator, String host, AtomicInteger ports) { super(client, addressTranslator); this.client = client; this.host = host; this.ports = ports; } @Override protected void initEventLoopGroup(HazelcastClientInstanceImpl client) { } @Override protected void startEventLoopGroup() { } @Override protected void stopEventLoopGroup() { } @Override protected ClientConnection createSocketConnection(Address address) throws IOException { if (!alive) { throw new HazelcastException("ConnectionManager is not active!!!"); } try { HazelcastInstance instance = nodeRegistry.getInstance(address); if (instance == null) { throw new IOException("Can not connected to " + address + ": instance does not exist"); } Node node = TestUtil.getNode(instance); Address localAddress = new Address(host, ports.incrementAndGet()); LockPair lockPair = getLockPair(address); MockedClientConnection connection = new MockedClientConnection(client, connectionIdGen.incrementAndGet(), node.nodeEngine, address, localAddress, lockPair); LOGGER.info("Created connection to endpoint: " + address + ", connection: " + connection); return connection; } catch (Exception e) { throw ExceptionUtil.rethrow(e, IOException.class); } } private LockPair getLockPair(Address address) { return ConcurrencyUtil.getOrPutIfAbsent(addressBlockMap, address, new ConstructorFunction<Address, LockPair>() { @Override public LockPair createNew(Address arg) { return new LockPair(new ReentrantReadWriteLock(), new ReentrantReadWriteLock()); } }); } /** * Blocks incoming messages to client from given address */ void blockFrom(Address address) { LOGGER.info("Blocked messages from " + address); LockPair lockPair = getLockPair(address); lockPair.blockIncoming(); } /** * Unblocks incoming messages to client from given address */ void unblockFrom(Address address) { LOGGER.info("Unblocked messages from " + address); LockPair lockPair = getLockPair(address); lockPair.unblockIncoming(); } /** * Blocks outgoing messages from client to given address */ void blockTo(Address address) { LOGGER.info("Blocked messages to " + address); LockPair lockPair = getLockPair(address); lockPair.blockOutgoing(); } /** * Unblocks outgoing messages from client to given address */ void unblockTo(Address address) { LOGGER.info("Unblocked messages to " + address); LockPair lockPair = getLockPair(address); lockPair.unblockOutgoing(); } } private class MockedClientConnection extends ClientConnection { private volatile long lastReadTime; private volatile long lastWriteTime; private final NodeEngineImpl serverNodeEngine; private final Address remoteAddress; private final Address localAddress; private final MockedNodeConnection serverSideConnection; private final TwoWayBlockableExecutor executor; MockedClientConnection(HazelcastClientInstanceImpl client, int connectionId, NodeEngineImpl serverNodeEngine, Address address, Address localAddress, LockPair lockPair) throws IOException { super(client, connectionId); this.serverNodeEngine = serverNodeEngine; this.remoteAddress = address; this.localAddress = localAddress; this.executor = new TwoWayBlockableExecutor(lockPair); this.serverSideConnection = new MockedNodeConnection(connectionId, remoteAddress, localAddress, serverNodeEngine, this); } @Override public void start() throws IOException { // no init for mock connections } void handleClientMessage(final ClientMessage clientMessage) { executor.executeIncoming(new Runnable() { @Override public void run() { lastReadTime = System.currentTimeMillis(); getConnectionManager().handleClientMessage(clientMessage, MockedClientConnection.this); } @Override public String toString() { return "Runnable message " + clientMessage + ", " + MockedClientConnection.this; } }); } @Override public boolean write(final OutboundFrame frame) { final Node node = serverNodeEngine.getNode(); if (node.getState() == NodeState.SHUT_DOWN) { return false; } executor.executeOutgoing(new Runnable() { @Override public String toString() { return "Runnable message " + frame + ", " + MockedClientConnection.this; } @Override public void run() { ClientMessage newPacket = readFromPacket((ClientMessage) frame); lastWriteTime = System.currentTimeMillis(); serverSideConnection.handleClientMessage(newPacket); } }); return true; } private ClientMessage readFromPacket(ClientMessage packet) { return ClientMessage.createForDecode(packet.buffer(), 0); } @Override public long lastReadTimeMillis() { return lastReadTime; } @Override public long lastWriteTimeMillis() { return lastWriteTime; } @Override public InetAddress getInetAddress() { try { return remoteAddress.getInetAddress(); } catch (UnknownHostException e) { e.printStackTrace(); return null; } } @Override public InetSocketAddress getRemoteSocketAddress() { try { return remoteAddress.getInetSocketAddress(); } catch (UnknownHostException e) { e.printStackTrace(); return null; } } @Override public int getPort() { return remoteAddress.getPort(); } @Override public InetSocketAddress getLocalSocketAddress() { try { return localAddress.getInetSocketAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } return null; } @Override protected void innerClose() throws IOException { executor.executeOutgoing((new Runnable() { @Override public void run() { serverSideConnection.close(null, null); } @Override public String toString() { return "Client Closed EOF. " + MockedClientConnection.this; } })); executor.shutdownIncoming(); } void onServerClose(final String reason) { executor.executeIncoming(new Runnable() { @Override public String toString() { return "Server Closed EOF. " + MockedClientConnection.this; } @Override public void run() { MockedClientConnection.this.close(reason, new TargetDisconnectedException("Mocked Remote socket closed")); } }); executor.shutdownOutgoing(); } @Override public String toString() { return "MockedClientConnection{" + "localAddress=" + localAddress + ", super=" + super.toString() + '}'; } } private class MockedNodeConnection extends MockConnection { private final MockedClientConnection responseConnection; private final int connectionId; private volatile long lastReadTimeMillis; private volatile long lastWriteTimeMillis; private volatile AtomicBoolean alive = new AtomicBoolean(true); MockedNodeConnection(int connectionId, Address localEndpoint, Address remoteEndpoint, NodeEngineImpl nodeEngine, MockedClientConnection responseConnection) { super(localEndpoint, remoteEndpoint, nodeEngine); this.responseConnection = responseConnection; this.connectionId = connectionId; register(); lastReadTimeMillis = System.currentTimeMillis(); lastWriteTimeMillis = System.currentTimeMillis(); } private void register() { Node node = remoteNodeEngine.getNode(); node.getConnectionManager().registerConnection(getEndPoint(), this); } @Override public boolean write(OutboundFrame frame) { final ClientMessage packet = (ClientMessage) frame; if (isAlive()) { lastWriteTimeMillis = System.currentTimeMillis(); ClientMessage newPacket = readFromPacket(packet); responseConnection.handleClientMessage(newPacket); return true; } return false; } void handleClientMessage(ClientMessage newPacket) { lastReadTimeMillis = System.currentTimeMillis(); remoteNodeEngine.getNode().clientEngine.handleClientMessage(newPacket, this); } @Override public boolean isClient() { return true; } private ClientMessage readFromPacket(ClientMessage packet) { return ClientMessage.createForDecode(packet.buffer(), 0); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MockedNodeConnection that = (MockedNodeConnection) o; if (connectionId != that.connectionId) { return false; } Address remoteEndpoint = getEndPoint(); return !(remoteEndpoint != null ? !remoteEndpoint.equals(that.getEndPoint()) : that.getEndPoint() != null); } @Override public void close(String reason, Throwable cause) { if (!alive.compareAndSet(true, false)) { return; } Logger.getLogger(MockedNodeConnection.class).warning("Server connection closed: " + reason, cause); super.close(reason, cause); responseConnection.onServerClose(reason); } @Override public int hashCode() { int result = connectionId; Address remoteEndpoint = getEndPoint(); result = 31 * result + (remoteEndpoint != null ? remoteEndpoint.hashCode() : 0); return result; } @Override public long lastReadTimeMillis() { return lastReadTimeMillis; } @Override public long lastWriteTimeMillis() { return lastWriteTimeMillis; } @Override public ConnectionType getType() { return ConnectionType.JAVA_CLIENT; } @Override public String toString() { return "MockedNodeConnection{" + " remoteEndpoint = " + getEndPoint() + ", localEndpoint = " + localEndpoint + ", connectionId = " + connectionId + '}'; } } }