package io.scalecube.transport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Network Emulator is allowing to control link quality between endpoints in order to allow testing of message loss,
* message delay, cluster partitions cluster recovery and other network related conditions.
*
* @author Anton Kharenko
*/
public final class NetworkEmulator {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkEmulator.class);
public static final NetworkLinkSettings DEAD_LINK_SETTINGS = new NetworkLinkSettings(100, 0);
public static final NetworkLinkSettings ALIVE_LINK_SETTINGS = new NetworkLinkSettings(0, 0);
private volatile NetworkLinkSettings defaultLinkSettings = ALIVE_LINK_SETTINGS;
private final Map<Address, NetworkLinkSettings> customLinkSettings = new ConcurrentHashMap<>();
private final AtomicLong totalMessageSentCount = new AtomicLong();
private final AtomicLong totalMessageLostCount = new AtomicLong();
private final boolean enabled;
private final Address address;
/**
* Creates new instance of network emulator. Should be always created internally by Transport.
*
* @param address local address
* @param enabled either network emulator is enabled
*/
NetworkEmulator(Address address, boolean enabled) {
this.address = address;
this.enabled = enabled;
}
/**
* Returns link settings applied to the given destination.
*/
public NetworkLinkSettings getLinkSettings(Address destination) {
return customLinkSettings.containsKey(destination) ? customLinkSettings.get(destination) : defaultLinkSettings;
}
/**
* Returns link settings applied to the given destination.
*/
public NetworkLinkSettings getLinkSettings(InetSocketAddress address) {
// Check hostname:port
Address address1 = Address.create(address.getHostName(), address.getPort());
if (customLinkSettings.containsKey(address1)) {
return customLinkSettings.get(address1);
}
// Check ip:port
Address address2 = Address.create(address.getAddress().getHostAddress(), address.getPort());
if (customLinkSettings.containsKey(address2)) {
return customLinkSettings.get(address2);
}
// Use default
return defaultLinkSettings;
}
/**
* Sets given network emulator settings for specific link. If network emulator is disabled do nothing.
*/
public void setLinkSettings(Address destination, int lossPercent, int meanDelay) {
if (!enabled) {
LOGGER.warn("Can't set network settings (loss={}%, mean={}ms) from {} to {} since network emulator is disabled",
lossPercent, meanDelay, address, destination);
return;
}
NetworkLinkSettings settings = new NetworkLinkSettings(lossPercent, meanDelay);
customLinkSettings.put(destination, settings);
LOGGER.info("Set network settings (loss={}%, mean={}ms) from {} to {}",
lossPercent, meanDelay, address, destination);
}
/**
* Sets default network emulator settings. If network emulator is disabled do nothing.
*/
public void setDefaultLinkSettings(int lossPercent, int meanDelay) {
if (!enabled) {
LOGGER.warn("Can't set default network settings (loss={}%, mean={}ms) for {} since network emulator is disabled",
lossPercent, meanDelay, address);
return;
}
defaultLinkSettings = new NetworkLinkSettings(lossPercent, meanDelay);
LOGGER.info("Set default network settings (loss={}%, mean={}ms) for {}", lossPercent, meanDelay, address);
}
/**
* Blocks messages to the given destinations. If network emulator is disabled do nothing.
*/
public void block(Address... destinations) {
block(Arrays.asList(destinations));
}
/**
* Blocks messages to the given destinations. If network emulator is disabled do nothing.
*/
public void block(Collection<Address> destinations) {
if (!enabled) {
LOGGER.warn("Can't block network from {} to {} since network emulator is disabled");
return;
}
for (Address destination : destinations) {
customLinkSettings.put(destination, DEAD_LINK_SETTINGS);
}
LOGGER.info("Blocked network from {} to {}", address, destinations);
}
/**
* Unblocks messages to given destinations. If network emulator is disabled do nothing.
*/
public void unblock(Address... destination) {
unblock(Arrays.asList(destination));
}
/**
* Unblocks messages to given destinations. If network emulator is disabled do nothing.
*/
public void unblock(Collection<Address> destinations) {
if (!enabled) {
LOGGER.warn("Can't unblock network from {} to {} since network emulator is disabled", address, destinations);
return;
}
for (Address destination : destinations) {
customLinkSettings.remove(destination);
}
LOGGER.info("Unblocked network from {} to {}", address, destinations);
}
/**
* Unblock messages to all destinations. If network emulator is disabled do nothing.
*/
public void unblockAll() {
if (!enabled) {
LOGGER.warn("Can't unblock network from {} since network emulator is disabled", address);
return;
}
customLinkSettings.clear();
LOGGER.info("Unblocked all network from {}", address);
}
/**
* Returns total message sent count computed by network emulator. If network emulator is disabled returns zero.
*/
public long totalMessageSentCount() {
if (!enabled) {
LOGGER.warn("Can't compute total messages sent from {} since network emulator is disabled", address);
return 0;
}
return totalMessageSentCount.get();
}
/**
* Returns total message lost count computed by network emulator. If network emulator is disabled returns zero.
*/
public long totalMessageLostCount() {
if (!enabled) {
LOGGER.warn("Can't compute total messages lost from {} since network emulator is disabled", address);
return 0;
}
return totalMessageLostCount.get();
}
// For internal use
void incrementMessageSentCount() {
if (!enabled) {
LOGGER.warn("since network emulator is disabled");
return;
}
totalMessageSentCount.incrementAndGet();
}
// For internal use
void incrementMessageLostCount() {
if (!enabled) {
LOGGER.warn("since network emulator is disabled");
return;
}
totalMessageLostCount.incrementAndGet();
}
}