io.scalecube.cluster.utils.NetworkEmulator Maven / Gradle / Ivy
package io.scalecube.cluster.utils;
import io.scalecube.cluster.transport.api.Message;
import io.scalecube.net.Address;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
/**
* 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.
*
* NOTE: used for test purposes.
*
*
NOTE: if emulator is disabled then side effect functions does nothing.
*/
public final class NetworkEmulator {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkEmulator.class);
private volatile OutboundSettings defaultOutboundSettings = new OutboundSettings(0, 0);
private volatile InboundSettings defaultInboundSettings = new InboundSettings(true);
private final Map
outboundSettings = new ConcurrentHashMap<>();
private final Map inboundSettings = new ConcurrentHashMap<>();
private final AtomicLong totalMessageSentCount = new AtomicLong();
private final AtomicLong totalOutboundMessageLostCount = new AtomicLong();
private final AtomicLong totalInboundMessageLostCount = new AtomicLong();
private final Address address;
/**
* Creates new instance of network emulator.
*
* @param address local address
*/
NetworkEmulator(Address address) {
this.address = address;
}
//// OUTBOUND functions
/**
* Returns network outbound settings applied to the given destination.
*
* @param destination address of target endpoint
* @return network outbound settings
*/
public OutboundSettings outboundSettings(Address destination) {
return outboundSettings.getOrDefault(destination, defaultOutboundSettings);
}
/**
* Setter for network emulator outbound settings for specific destination.
*
* @param destination address of target endpoint
* @param lossPercent loss in percents
* @param meanDelay mean delay
*/
public void outboundSettings(Address destination, int lossPercent, int meanDelay) {
OutboundSettings settings = new OutboundSettings(lossPercent, meanDelay);
outboundSettings.put(destination, settings);
LOGGER.debug("[{}] Set outbound settings {} to {}", address, settings, destination);
}
/**
* Setter for network emulator outbound settings.
*
* @param lossPercent loss in percents
* @param meanDelay mean delay
*/
public void setDefaultOutboundSettings(int lossPercent, int meanDelay) {
defaultOutboundSettings = new OutboundSettings(lossPercent, meanDelay);
LOGGER.debug("[{}] Set default outbound settings {}", address, defaultOutboundSettings);
}
/** Blocks outbound messages to all destinations. */
public void blockAllOutbound() {
outboundSettings.clear();
setDefaultOutboundSettings(100, 0);
LOGGER.debug("[{}] Blocked outbound to all destinations", address);
}
/** Unblocks outbound messages to all destinations. */
public void unblockAllOutbound() {
outboundSettings.clear();
setDefaultOutboundSettings(0, 0);
LOGGER.debug("[{}] Unblocked outbound to all destinations", address);
}
/**
* Blocks outbound messages to the given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void blockOutbound(Address... destinations) {
blockOutbound(Arrays.asList(destinations));
}
/**
* Blocks outbound messages to the given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void blockOutbound(Collection destinations) {
for (Address destination : destinations) {
outboundSettings.put(destination, new OutboundSettings(100, 0));
}
LOGGER.debug("[{}] Blocked outbound to {}", address, destinations);
}
/**
* Unblocks outbound messages to given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void unblockOutbound(Address... destinations) {
unblockOutbound(Arrays.asList(destinations));
}
/**
* Unblocks outbound messages to given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void unblockOutbound(Collection destinations) {
destinations.forEach(outboundSettings::remove);
LOGGER.debug("[{}] Unblocked outbound {}", address, destinations);
}
/**
* Returns total message sent count computed by network emulator.
*
* @return total message sent; 0 if emulator is disabled
*/
public long totalMessageSentCount() {
return totalMessageSentCount.get();
}
/**
* Returns total outbound message lost count computed by network emulator.
*
* @return total message lost; 0 if emulator is disabled
*/
public long totalOutboundMessageLostCount() {
return totalOutboundMessageLostCount.get();
}
/**
* Conditionally fails given outbound message onto given address with {@link
* NetworkEmulatorException}.
*
* @param msg outbound message
* @param address target address
* @return mono message
*/
public Mono tryFailOutbound(Message msg, Address address) {
return Mono.defer(
() -> {
totalMessageSentCount.incrementAndGet();
// Emulate message loss
boolean isLost = outboundSettings(address).evaluateLoss();
if (isLost) {
totalOutboundMessageLostCount.incrementAndGet();
return Mono.error(
new NetworkEmulatorException("NETWORK_BREAK detected, didn't send " + msg));
} else {
return Mono.just(msg);
}
});
}
/**
* Conditionally delays given outbound message onto given address.
*
* @param msg outbound message
* @param address target address
* @return mono message
*/
public Mono tryDelayOutbound(Message msg, Address address) {
return Mono.defer(
() -> {
totalMessageSentCount.incrementAndGet();
// Emulate message delay
int delay = (int) outboundSettings(address).evaluateDelay();
if (delay > 0) {
return Mono.just(msg).delayElement(Duration.ofMillis(delay));
} else {
return Mono.just(msg);
}
});
}
//// INBOUND functions
/**
* Returns network inbound settings applied to the given destination.
*
* @param destination address of target endpoint
* @return network inbound settings
*/
public InboundSettings inboundSettings(Address destination) {
return inboundSettings.getOrDefault(destination, defaultInboundSettings);
}
/**
* Setter for network emulator inbound settings for specific destination.
*
* @param shallPass shallPass inbound flag
*/
public void inboundSettings(Address destination, boolean shallPass) {
InboundSettings settings = new InboundSettings(shallPass);
inboundSettings.put(destination, settings);
LOGGER.debug("[{}] Set inbound settings {} to {}", address, settings, destination);
}
/**
* Setter for network emulator inbound settings.
*
* @param shallPass shallPass inbound flag
*/
public void setDefaultInboundSettings(boolean shallPass) {
defaultInboundSettings = new InboundSettings(shallPass);
LOGGER.debug("[{}] Set default inbound settings {}", address, defaultInboundSettings);
}
/** Blocks inbound messages from all destinations. */
public void blockAllInbound() {
inboundSettings.clear();
setDefaultInboundSettings(false);
LOGGER.debug("[{}] Blocked inbound from all destinations", address);
}
/** Unblocks inbound messages to all destinations. */
public void unblockAllInbound() {
inboundSettings.clear();
setDefaultInboundSettings(true);
LOGGER.debug("[{}] Unblocked inbound from all destinations", address);
}
/**
* Blocks inbound messages to the given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void blockInbound(Address... destinations) {
blockInbound(Arrays.asList(destinations));
}
/**
* Blocks inbound messages to the given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void blockInbound(Collection destinations) {
for (Address destination : destinations) {
inboundSettings.put(destination, new InboundSettings(false));
}
LOGGER.debug("[{}] Blocked inbound from {}", address, destinations);
}
/**
* Unblocks inbound messages to given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void unblockInbound(Address... destinations) {
unblockInbound(Arrays.asList(destinations));
}
/**
* Unblocks inbound messages to given destinations.
*
* @param destinations collection of target endpoints where to apply
*/
public void unblockInbound(Collection destinations) {
destinations.forEach(inboundSettings::remove);
LOGGER.debug("[{}] Unblocked inbound from {}", address, destinations);
}
/**
* Returns total inbound message lost count computed by network emulator.
*
* @return total message lost; 0 if emulator is disabled
*/
public long totalInboundMessageLostCount() {
return totalInboundMessageLostCount.get();
}
/**
* This class contains settings and computations for the network outbound link to evaluate message
* loss and message delay on outbound path. Parameters:
*
*
* - Percent of losing messages.
*
- Mean network delays in milliseconds. Delays are emulated using exponential distribution
* of probabilities.
*
*/
public static final class OutboundSettings {
private final int lossPercent;
private final int meanDelay;
/**
* Constructor for outbound link settings.
*
* @param lossPercent loss in percent
* @param meanDelay mean dealy
*/
public OutboundSettings(int lossPercent, int meanDelay) {
this.lossPercent = lossPercent;
this.meanDelay = meanDelay;
}
/**
* Returns probability of message loss in percents.
*
* @return loss in percents
*/
public int lossPercent() {
return lossPercent;
}
/**
* Returns mean network delay for message in milliseconds.
*
* @return mean delay
*/
public int meanDelay() {
return meanDelay;
}
/**
* Indicator function telling is loss enabled.
*
* @return boolean indicating would loss occur
*/
public boolean evaluateLoss() {
return lossPercent > 0
&& (lossPercent >= 100 || ThreadLocalRandom.current().nextInt(100) < lossPercent);
}
/**
* Evaluates network delay according to exponential distribution of probabilities.
*
* @return delay
*/
public long evaluateDelay() {
if (meanDelay > 0) {
// Network delays (network delays). Delays should be emulated using exponential distribution
// of probabilities.
// log(1-x)/(1/mean)
double x0 = ThreadLocalRandom.current().nextDouble();
double y0 = -Math.log(1 - x0) * meanDelay;
return (long) y0;
}
return 0;
}
@Override
public String toString() {
return new StringJoiner(", ", OutboundSettings.class.getSimpleName() + "[", "]")
.add("lossPercent=" + lossPercent)
.add("meanDelay=" + meanDelay)
.toString();
}
}
/**
* This class contains settings and computations for the network inbound link to evaluate message
* blocking on inbound path. Parameters:
*
*
* - ShallPass boolean flag for controlling inbound messages.
*
*/
public static class InboundSettings {
private final boolean shallPass;
/**
* Constructor for inbound link settings.
*
* @param shallPass shall pass flag
*/
public InboundSettings(boolean shallPass) {
this.shallPass = shallPass;
}
/**
* Returns shallPass inbound flag.
*
* @return shallPass flag
*/
public boolean shallPass() {
return shallPass;
}
@Override
public String toString() {
return new StringJoiner(", ", InboundSettings.class.getSimpleName() + "[", "]")
.add("shallPass=" + shallPass)
.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy