String currentOwner = getCurrentOwner(selectOwner.getEntityPath());
if(Strings.isNullOrEmpty(currentOwner)) {
String entityType = EntityOwnersModel.entityTypeFromEntityPath(selectOwner.getEntityPath());
- writeNewOwner(selectOwner.getEntityPath(), newOwner(selectOwner.getAllCandidates(),
+ writeNewOwner(selectOwner.getEntityPath(), newOwner(currentOwner, selectOwner.getAllCandidates(),
entityOwnershipStatistics.byEntityType(entityType),
selectOwner.getOwnerSelectionStrategy()));
newLeader, isLeader);
if(isLeader) {
+
+ // Clear all existing strategies so that they get re-created when we call createStrategy again
+ // This allows the strategies to be re-initialized with existing statistics maintained by
+ // EntityOwnershipStatistics
+ strategyConfig.clearStrategies();
// We were just elected leader. If the old leader is down, select new owners for the entities
// owned by the down leader.
if(message.getRemovedCandidate().equals(currentOwner) || message.getRemainingCandidates().size() == 0){
String entityType = EntityOwnersModel.entityTypeFromEntityPath(message.getEntityPath());
writeNewOwner(message.getEntityPath(),
- newOwner(message.getRemainingCandidates(), entityOwnershipStatistics.byEntityType(entityType),
+ newOwner(currentOwner, message.getRemainingCandidates(), entityOwnershipStatistics.byEntityType(entityType),
getEntityOwnerElectionStrategy(message.getEntityPath())));
}
} else {
private EntityOwnerSelectionStrategy getEntityOwnerElectionStrategy(YangInstanceIdentifier entityPath) {
final String entityType = EntityOwnersModel.entityTypeFromEntityPath(entityPath);
- return strategyConfig.createStrategy(entityType);
+ return strategyConfig.createStrategy(entityType, entityOwnershipStatistics.byEntityType(entityType));
}
private void onCandidateAdded(CandidateAdded message) {
LOG.debug("{}: Using strategy {} to select owner", persistenceId(), strategy);
if(Strings.isNullOrEmpty(currentOwner)){
if(strategy.getSelectionDelayInMillis() == 0L) {
- writeNewOwner(message.getEntityPath(), newOwner(message.getAllCandidates(),
+ writeNewOwner(message.getEntityPath(), newOwner(currentOwner, message.getAllCandidates(),
entityOwnershipStatistics.byEntityType(entityType), strategy));
} else if(message.getAllCandidates().size() == availableMembers) {
LOG.debug("{}: Received the maximum candidates requests : {} writing new owner",
persistenceId(), availableMembers);
cancelOwnerSelectionTask(message.getEntityPath());
- writeNewOwner(message.getEntityPath(), newOwner(message.getAllCandidates(),
+ writeNewOwner(message.getEntityPath(), newOwner(currentOwner, message.getAllCandidates(),
entityOwnershipStatistics.byEntityType(entityType), strategy));
} else {
scheduleOwnerSelection(message.getEntityPath(), message.getAllCandidates(), strategy);
}
}
- private String newOwner(Collection<String> candidates, Map<String, Long> statistics, EntityOwnerSelectionStrategy ownerSelectionStrategy) {
+ private String newOwner(String currentOwner, Collection<String> candidates, Map<String, Long> statistics, EntityOwnerSelectionStrategy ownerSelectionStrategy) {
Collection<String> viableCandidates = getViableCandidates(candidates);
if(viableCandidates.size() == 0){
return "";
}
- return ownerSelectionStrategy.newOwner(viableCandidates, statistics);
+ return ownerSelectionStrategy.newOwner(currentOwner, viableCandidates);
}
private Collection<String> getViableCandidates(Collection<String> candidates) {
package org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy;
+import java.util.Map;
+
public abstract class AbstractEntityOwnerSelectionStrategy implements EntityOwnerSelectionStrategy {
private final long selectionDelayInMillis;
+ private final Map<String, Long> initialStatistics;
- protected AbstractEntityOwnerSelectionStrategy(long selectionDelayInMillis) {
+ protected AbstractEntityOwnerSelectionStrategy(long selectionDelayInMillis, Map<String, Long> initialStatistics) {
this.selectionDelayInMillis = selectionDelayInMillis;
+ this.initialStatistics = initialStatistics;
}
@Override
public long getSelectionDelayInMillis() {
return selectionDelayInMillis;
}
+
+ public Map<String, Long> getInitialStatistics() {
+ return initialStatistics;
+ }
}
package org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy;
import java.util.Collection;
-import java.util.Map;
+import javax.annotation.Nullable;
/**
* An EntityOwnerSelectionStrategy is to be used by the EntityOwnershipShard to select a new owner from a collection
/**
- *
+ * @param currentOwner the current owner of the entity if any, null otherwise
* @param viableCandidates the available candidates from which to choose the new owner
- * @param statistics contains a snapshot of a mapping between candidate names and the number of entities
- * owned by that candidate
* @return the new owner
*/
- String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics);
+ String newOwner(@Nullable String currentOwner, Collection<String> viableCandidates);
}
return entityTypeToStrategyInfo.get(entityType) != null;
}
- public EntityOwnerSelectionStrategy createStrategy(final String entityType) {
+ public EntityOwnerSelectionStrategy createStrategy(String entityType, Map<String, Long> initialStatistics){
final EntityOwnerSelectionStrategy strategy;
final EntityOwnerSelectionStrategy existingStrategy = entityTypeToOwnerSelectionStrategy.get(entityType);
if(existingStrategy != null){
if(strategyInfo == null){
strategy = FirstCandidateSelectionStrategy.INSTANCE;
} else {
- strategy = strategyInfo.createStrategy();
+ strategy = strategyInfo.createStrategy(initialStatistics);
}
entityTypeToOwnerSelectionStrategy.put(entityType, strategy);
}
* using it.
*/
@Deprecated
+ public void clearStrategies() {
+ entityTypeToOwnerSelectionStrategy.clear();
+ }
+
private static final class StrategyInfo {
private final Class<? extends EntityOwnerSelectionStrategy> strategyClass;
private final long delay;
this.delay = delay;
}
- public EntityOwnerSelectionStrategy createStrategy() {
+ public EntityOwnerSelectionStrategy createStrategy(Map<String, Long> initialStatistics){
try {
- return strategyClass.getDeclaredConstructor(long.class).newInstance(delay);
+ return strategyClass.getDeclaredConstructor(long.class, Map.class).newInstance(delay, initialStatistics);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
LOG.warn("could not create custom strategy", e);
}
import com.google.common.base.Preconditions;
import java.util.Collection;
+import java.util.Collections;
import java.util.Map;
/**
*/
public class FirstCandidateSelectionStrategy extends AbstractEntityOwnerSelectionStrategy {
- public static final FirstCandidateSelectionStrategy INSTANCE = new FirstCandidateSelectionStrategy(0L);
+ public static final FirstCandidateSelectionStrategy INSTANCE = new FirstCandidateSelectionStrategy(0L, Collections.emptyMap());
- public FirstCandidateSelectionStrategy(long selectionDelayInMillis) {
- super(selectionDelayInMillis);
+ public FirstCandidateSelectionStrategy(long selectionDelayInMillis, Map<String, Long> initialStatistics) {
+ super(selectionDelayInMillis, initialStatistics);
}
@Override
- public String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics) {
+ public String newOwner(String currentOwner, Collection<String> viableCandidates) {
Preconditions.checkArgument(viableCandidates.size() > 0, "No viable candidates provided");
return viableCandidates.iterator().next();
}
package org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
private Map<String, Long> localStatistics = new HashMap<>();
- protected LeastLoadedCandidateSelectionStrategy(long selectionDelayInMillis) {
- super(selectionDelayInMillis);
+ protected LeastLoadedCandidateSelectionStrategy(long selectionDelayInMillis, Map<String, Long> initialStatistics) {
+ super(selectionDelayInMillis, initialStatistics);
+
+ localStatistics.putAll(initialStatistics);
}
@Override
- public String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics) {
+ public String newOwner(String currentOwner, Collection<String> viableCandidates) {
+ Preconditions.checkArgument(viableCandidates.size() > 0);
String leastLoadedCandidate = null;
long leastLoadedCount = Long.MAX_VALUE;
+ if(!Strings.isNullOrEmpty(currentOwner)){
+ long localVal = MoreObjects.firstNonNull(localStatistics.get(currentOwner), 0L);
+ localStatistics.put(currentOwner, localVal - 1);
+ }
+
for(String candidateName : viableCandidates){
- long val = MoreObjects.firstNonNull(statistics.get(candidateName), 0L);
- long localVal = MoreObjects.firstNonNull(localStatistics.get(candidateName), 0L);
- if(val < localVal){
- LOG.debug("Local statistic higher - Candidate : {}, local statistic : {}, provided statistic : {}",
- candidateName, localVal, val);
- val = localVal;
- } else {
- LOG.debug("Provided statistic higher - Candidate : {}, local statistic : {}, provided statistic : {}",
- candidateName, localVal, val);
- localStatistics.put(candidateName, val);
- }
+ long val = MoreObjects.firstNonNull(localStatistics.get(candidateName), 0L);
if(val < leastLoadedCount){
leastLoadedCount = val;
leastLoadedCandidate = candidateName;
localStatistics.put(leastLoadedCandidate, leastLoadedCount + 1);
return leastLoadedCandidate;
}
+
+ @VisibleForTesting
+ Map<String, Long> getLocalStatistics(){
+ return localStatistics;
+ }
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import java.io.IOException;
+import java.util.Collections;
import java.util.Hashtable;
import org.junit.Before;
import org.junit.Test;
assertTrue(config.isStrategyConfigured("test"));
- EntityOwnerSelectionStrategy strategy = config.createStrategy("test");
- assertTrue(strategy instanceof LastCandidateSelectionStrategy);
+ EntityOwnerSelectionStrategy strategy = config.createStrategy("test", Collections.<String, Long>emptyMap());
+ assertTrue(strategy.toString(), strategy instanceof LastCandidateSelectionStrategy);
assertEquals(100L, strategy.getSelectionDelayInMillis());
+
+ final EntityOwnerSelectionStrategy strategy1 = config.createStrategy("test", Collections.<String, Long>emptyMap());
+ assertEquals(strategy, strategy1);
+
+ config.clearStrategies();
+
+ final EntityOwnerSelectionStrategy strategy2 = config.createStrategy("test", Collections.<String, Long>emptyMap());
+ assertNotEquals(strategy1, strategy2);
}
@Test
EntityOwnerSelectionStrategyConfig config = loadStrategyConfig();
- assertEquals(100, config.createStrategy("test").getSelectionDelayInMillis());
- assertEquals(0, config.createStrategy("test2").getSelectionDelayInMillis());
+ assertEquals(100, config.createStrategy("test", Collections.<String, Long>emptyMap()).getSelectionDelayInMillis());
+ assertEquals(0, config.createStrategy("test2", Collections.<String, Long>emptyMap()).getSelectionDelayInMillis());
}
}
\ No newline at end of file
import java.util.Map;
public class LastCandidateSelectionStrategy extends AbstractEntityOwnerSelectionStrategy {
- public LastCandidateSelectionStrategy(final long selectionDelayInMillis) {
- super(selectionDelayInMillis);
+ public LastCandidateSelectionStrategy(long selectionDelayInMillis, Map<String, Long> initialStatistics) {
+ super(selectionDelayInMillis, initialStatistics);
}
@Override
- public String newOwner(final Collection<String> viableCandidates, final Map<String, Long> statistics) {
+ public String newOwner(String currentOwner, Collection<String> viableCandidates) {
return Iterables.getLast(viableCandidates);
}
}
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
@Test
public void testLeastLoadedStrategy(){
- LeastLoadedCandidateSelectionStrategy strategy = new LeastLoadedCandidateSelectionStrategy(0L);
+ LeastLoadedCandidateSelectionStrategy strategy = new LeastLoadedCandidateSelectionStrategy(0L, Collections.<String, Long>emptyMap());
- String owner = strategy.newOwner(prepareViableCandidates(3), new HashMap<String, Long>());
+ String owner = strategy.newOwner(null, prepareViableCandidates(3));
assertEquals("member-1", owner);
+ Map<String, Long> localStatistics = strategy.getLocalStatistics();
+ assertEquals(1L, (long) localStatistics.get("member-1"));
+
// member-2 has least load
- owner = strategy.newOwner(prepareViableCandidates(3), prepareStatistics(5,2,4));
+ strategy = new LeastLoadedCandidateSelectionStrategy(0L, prepareStatistics(5,2,4));
+ owner = strategy.newOwner(null, prepareViableCandidates(3));
assertEquals("member-2", owner);
+ assertStatistics(strategy.getLocalStatistics(), 5,3,4);
+
// member-3 has least load
- owner = strategy.newOwner(prepareViableCandidates(3), prepareStatistics(5,7,4));
+ strategy = new LeastLoadedCandidateSelectionStrategy(0L, prepareStatistics(5,7,4));
+ owner = strategy.newOwner(null, prepareViableCandidates(3));
assertEquals("member-3", owner);
+ assertStatistics(strategy.getLocalStatistics(), 5,7,5);
+
// member-1 has least load
- owner = strategy.newOwner(prepareViableCandidates(3), prepareStatistics(1,7,4));
+ strategy = new LeastLoadedCandidateSelectionStrategy(0L, prepareStatistics(1,7,4));
+ owner = strategy.newOwner(null, prepareViableCandidates(3));
+ assertEquals("member-1", owner);
+
+ assertStatistics(strategy.getLocalStatistics(), 2,7,4);
+
+ // Let member-3 become the owner
+ strategy = new LeastLoadedCandidateSelectionStrategy(0L, prepareStatistics(3,3,0));
+ owner = strategy.newOwner(null, prepareViableCandidates(3));
+ assertEquals("member-3", owner);
+
+ assertStatistics(strategy.getLocalStatistics(), 3,3,1);
+
+ // member-3 is no longer viable so choose a new owner
+ owner = strategy.newOwner("member-3", prepareViableCandidates(2));
assertEquals("member-1", owner);
+ assertStatistics(strategy.getLocalStatistics(), 4,3,0);
+
}
private static Map<String, Long> prepareStatistics(long... count){
}
return viableCandidates;
}
+
+ private void assertStatistics(Map<String, Long> statistics, long... count){
+ for(int i=0;i<count.length;i++){
+ assertEquals(count[i], (long) statistics.get("member-" + (i+1)));
+ }
+ }
}
\ No newline at end of file