package org.radargun.stages.topology;
import java.util.EnumSet;
import java.util.List;
import org.radargun.DistStageAck;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.stages.AbstractDistStage;
import org.radargun.traits.Clustered;
import org.radargun.traits.InjectTrait;
import org.radargun.traits.TopologyHistory;
import org.radargun.utils.TimeConverter;
import org.radargun.utils.TimeService;
import static org.radargun.traits.TopologyHistory.HistoryType;
@Stage(doc = "Waits for a period without any change in membership/topology history.")
public class WaitForTopologySettleStage extends AbstractDistStage {
@Property(doc = "How long period without any change are we looking for. Default is 10 seconds.", converter = TimeConverter.class)
public long period = 10000;
@Property(doc = "How long should we wait until we give up with error, 0 means indefinitely. Default is 10 minutes.", converter = TimeConverter.class)
public long timeout = 600000;
@Property(doc = "Name of the cache where we detect the events. Default is the default cache.")
public String cacheName;
@Property(doc = "Type of events to check in this stage. Default are TOPOLOGY, REHASH, CACHE_STATUS (see org.radargun.traits.TopologyHistory.HistoryType).")
public EnumSet<TopologyHistory.HistoryType> checkEvents = EnumSet.allOf(TopologyHistory.HistoryType.class);
@Property(doc = "Wait for cluster membership to settle. Default is true (if the Clustered trait is supported).")
public boolean checkMembership = true;
@InjectTrait(dependency = InjectTrait.Dependency.MANDATORY)
private TopologyHistory history;
@InjectTrait
private Clustered clustered;
@Override
public DistStageAck executeOnSlave() {
if (!isServiceRunning()) {
return successfulResponse();
}
long startTime = TimeService.currentTimeMillis();
for (; ; ) {
long now = TimeService.currentTimeMillis();
if (now >= startTime + timeout) {
return errorResponse("The topology has not settled within timeout.");
}
boolean settled = true;
if (checkEvents.contains(HistoryType.TOPOLOGY)) {
settled = settled && checkEventHistory(history.getTopologyChangeHistory(cacheName), "Topology change", now);
}
if (checkEvents.contains(HistoryType.REHASH)) {
settled = settled && checkEventHistory(history.getRehashHistory(cacheName), "Rehash", now);
}
if (checkEvents.contains(HistoryType.CACHE_STATUS)) {
settled = settled && checkEventHistory(history.getCacheStatusChangeHistory(cacheName), "Cache status change", now);
}
if (checkMembership) {
List<Clustered.Membership> membershipHistory = clustered == null ? null : clustered.getMembershipHistory();
settled = settled && checkMembership(membershipHistory, now);
}
if (settled) {
return successfulResponse();
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return errorResponse("Waiting interrupted", e);
}
}
}
}
/**
* @return Boolean indicating whether Topology/Rehash/Partition status has settled.
*/
private boolean checkEventHistory(List<TopologyHistory.Event> eventList, String eventName, long startDateMillis) {
int finished = 0;
for (int i = eventList.size() - 1; i >= 0; i--) {
TopologyHistory.Event event = eventList.get(i);
if (startDateMillis < event.getTime().getTime() + period) {
log.debugf("%s event finished too recently: %s", eventName, event);
return false;
}
switch (event.getType()) {
case START:
if (finished <= 0) {
log.debugf("%s event has not finished: %s", eventName, event);
return false;
} else {
finished--;
}
break;
case END:
finished++;
break;
case SINGLE:
// no op
}
}
if (finished != 0) {
log.warn("Number of START / END events doesn't match");
}
return true;
}
private boolean checkMembership(List<Clustered.Membership> membershipHistory, long startDateMillis) {
if (checkMembership && membershipHistory != null && !membershipHistory.isEmpty()) {
Clustered.Membership membership = membershipHistory.get(membershipHistory.size() - 1);
if (startDateMillis < membership.date.getTime() + period) {
log.debug("Last membership changed too recently: " + membership);
return false;
}
}
return true;
}
}