com.netflix.dyno.connectionpool.impl.HostStatusTracker Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2011 Netflix
*
* 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.netflix.dyno.connectionpool.impl;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import com.netflix.dyno.connectionpool.ConnectionPool;
import com.netflix.dyno.connectionpool.Host;
import com.netflix.dyno.connectionpool.Host.Status;
import com.netflix.dyno.connectionpool.HostConnectionPool;
import com.netflix.dyno.connectionpool.HostSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class that can be used in conjunction with a {@link HostSupplier} repeatedly to understand whether the change within the
* active and inactive host set.
*
* Implementations of {@link ConnectionPool} can then use this utility to adapt to topology changes and hence manage the corresponding
* {@link HostConnectionPool} objects for the set of active hosts.
*
* Note the behavior of this class is such that if a host disappears and it was last known as active
* then it will be moved to inactive. If however it disappears and its last known state was inactive
* then it is removed from tracking altogether. This is to support terminations/node replacements.
*
* @author poberai
*/
public class HostStatusTracker {
private static final Logger logger = LoggerFactory.getLogger(HostStatusTracker.class);
// the set of active and inactive hosts
private final Set activeHosts = new HashSet();
private final Set inactiveHosts = new HashSet();
public HostStatusTracker() {
}
public HostStatusTracker(Collection up, Collection down) {
verifyMutuallyExclusive(up, down);
activeHosts.addAll(up);
inactiveHosts.addAll(down);
}
/**
* Helper method to check that there is no overlap b/w hosts up and down.
*
* @param A
* @param B
*/
private void verifyMutuallyExclusive(Collection A, Collection B) {
Set left = new HashSet(A);
Set right = new HashSet(B);
boolean modified = left.removeAll(right);
if (modified) {
throw new RuntimeException("Host up and down sets are not mutually exclusive!");
}
}
/**
* All we need to check here is that whether the new active set is not exactly the same as the
* prev active set. If there are any new hosts that have been added or any hosts that are missing
* then return 'true' indicating that the active set has changed.
*
* @param hostsUp
* @return true/false indicating whether the active set has changed from the previous set.
*/
public boolean activeSetChanged(Collection hostsUp) {
return !hostsUp.equals(activeHosts);
}
/**
* This check is more involved than the active set check. Here we 2 conditions to check for
*
* 1. We could have new hosts that were in the active set and have shown up in the inactive set.
* 2. We can also have the case where hosts from the active set have disappeared and also not in the provided inactive set.
* This is where we have simply forgotten about some active host and that it needs to be shutdown
*
* @param hostsUp
* @param hostsDown
* @return true/false indicating whether we have a host that has been shutdown
*/
public boolean inactiveSetChanged(Collection hostsUp, Collection hostsDown) {
boolean newInactiveHostsFound = false;
// Check for condition 1.
for (Host hostDown : hostsDown) {
if (activeHosts.contains(hostDown)) {
newInactiveHostsFound = true;
break;
}
}
// Check for condition 2.
Set prevActiveHosts = new HashSet(activeHosts);
prevActiveHosts.removeAll(hostsUp);
newInactiveHostsFound = !prevActiveHosts.isEmpty();
return newInactiveHostsFound;
}
/**
* Helper method that checks if anything has changed b/w the current state and the new set of hosts up and down
*
* @param hostsUp
* @param hostsDown
* @return true/false indicating whether the set of hosts has changed or not.
*/
public boolean checkIfChanged(Collection hostsUp, Collection hostsDown) {
boolean changed = activeSetChanged(hostsUp) || inactiveSetChanged(hostsUp, hostsDown);
if (changed && logger.isDebugEnabled()) {
Set changedHostsUp = new HashSet<>(hostsUp);
changedHostsUp.removeAll(activeHosts);
changedHostsUp.forEach(x -> logger.debug("New host up: {}", x.getHostAddress()));
Set changedHostsDown = new HashSet<>(hostsDown);
changedHostsDown.removeAll(inactiveHosts);
changedHostsDown.forEach(x -> logger.debug("New host down: {}", x.getHostAddress()));
}
return changed;
}
/**
* Helper method that actually changes the state of the class to reflect the new set of hosts up and down
* Note that the new HostStatusTracker is returned that holds onto the new state. Calling classes must update their
* references to use the new HostStatusTracker
*
* @param hostsUp
* @param hostsDown
* @return
*/
public HostStatusTracker computeNewHostStatus(Collection hostsUp, Collection hostsDown) {
verifyMutuallyExclusive(hostsUp, hostsDown);
Set nextActiveHosts = new HashSet(hostsUp);
// Get the hosts that are currently down
Set nextInactiveHosts = new HashSet(hostsDown);
// add any previous hosts that were currently down iff they are still reported by the HostSupplier
Set union = new HashSet<>(hostsUp);
union.addAll(hostsDown);
if (!union.containsAll(inactiveHosts)) {
logger.info("REMOVING at least one inactive host from {} b/c it is no longer reported by HostSupplier",
inactiveHosts);
inactiveHosts.retainAll(union);
}
nextInactiveHosts.addAll(inactiveHosts);
// Now remove from the total set of inactive hosts any host that is currently up.
// This typically happens when a host moves from the inactive state to the active state.
// And hence it will be there in the prev inactive set, and will also be there in the new active set
// for this round.
for (Host host : nextActiveHosts) {
nextInactiveHosts.remove(host);
}
// Now add any host that is not in the new active hosts set and that was in the previous active set
Set prevActiveHosts = new HashSet(activeHosts);
prevActiveHosts.removeAll(hostsUp);
// If anyone is remaining in the prev set then add it to the inactive set, since it has gone away
nextInactiveHosts.addAll(prevActiveHosts);
for (Host host : nextActiveHosts) {
host.setStatus(Status.Up);
}
for (Host host : nextInactiveHosts) {
host.setStatus(Status.Down);
}
return new HostStatusTracker(nextActiveHosts, nextInactiveHosts);
}
public boolean isHostUp(Host host) {
return activeHosts.contains(host);
}
public Collection getActiveHosts() {
return activeHosts;
}
public Collection getInactiveHosts() {
return inactiveHosts;
}
/**
* Returns the total number of hosts being tracked by this instance. Note that this is calculated
* on every invocation.
*
* @return Integer
*/
public int getHostCount() {
// The host collections are never null since they are initialized during construction of this instance.
return activeHosts.size() + inactiveHosts.size();
}
public String toString() {
return "HostStatusTracker \nactiveSet: " + activeHosts.toString() + "\ninactiveSet: " + inactiveHosts.toString();
}
}