Implement LeastLoadedCandidateSelectionStrategy 07/29407/5
authorMoiz Raja <moraja@cisco.com>
Sun, 11 Oct 2015 20:52:52 +0000 (13:52 -0700)
committerTom Pantelis <tpanteli@brocade.com>
Fri, 13 Nov 2015 00:40:26 +0000 (19:40 -0500)
Change-Id: I09035505bcfa0ef5b2ac357217186ad98db7974c
Signed-off-by: Moiz Raja <moraja@cisco.com>
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnershipShard.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnershipStatistics.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/EntityOwnerSelectionStrategy.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/FirstCandidateSelectionStrategy.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LeastLoadedCandidateSelectionStrategy.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LastCandidateSelectionStrategy.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LeastLoadedCandidateSelectionStrategyTest.java [new file with mode: 0644]

index 60fab39b8e856f98bfbe47f3dc44e43d51bedf82..d104222b9ce8d68685ddb8d859189a52930af13c 100644 (file)
@@ -142,7 +142,9 @@ class EntityOwnershipShard extends Shard {
     private void onSelectOwner(SelectOwner selectOwner) {
         String currentOwner = getCurrentOwner(selectOwner.getEntityPath());
         if(Strings.isNullOrEmpty(currentOwner)) {
+            String entityType = EntityOwnersModel.entityTypeFromEntityPath(selectOwner.getEntityPath());
             writeNewOwner(selectOwner.getEntityPath(), newOwner(selectOwner.getAllCandidates(),
+                    entityOwnershipStatistics.byEntityType(entityType),
                     selectOwner.getOwnerSelectionStrategy()));
 
             Cancellable cancellable = entityToScheduledOwnershipTask.get(selectOwner.getEntityPath());
@@ -272,8 +274,10 @@ class EntityOwnershipShard extends Shard {
         if(isLeader()) {
             String currentOwner = getCurrentOwner(message.getEntityPath());
             if(message.getRemovedCandidate().equals(currentOwner)){
+                String entityType = EntityOwnersModel.entityTypeFromEntityPath(message.getEntityPath());
                 writeNewOwner(message.getEntityPath(),
-                        newOwner(message.getRemainingCandidates(), getEntityOwnerElectionStrategy(message.getEntityPath())));
+                        newOwner(message.getRemainingCandidates(), entityOwnershipStatistics.byEntityType(entityType),
+                                getEntityOwnerElectionStrategy(message.getEntityPath())));
             }
         } else {
             // We're not the leader. If the removed candidate is our local member then check if we actually
@@ -313,7 +317,9 @@ class EntityOwnershipShard extends Shard {
         EntityOwnerSelectionStrategy strategy = getEntityOwnerElectionStrategy(message.getEntityPath());
         if(Strings.isNullOrEmpty(currentOwner)){
             if(strategy.getSelectionDelayInMillis() == 0L) {
-                writeNewOwner(message.getEntityPath(), newOwner(message.getAllCandidates(), strategy));
+                String entityType = EntityOwnersModel.entityTypeFromEntityPath(message.getEntityPath());
+                writeNewOwner(message.getEntityPath(), newOwner(message.getAllCandidates(),
+                        entityOwnershipStatistics.byEntityType(entityType), strategy));
             } else {
                 scheduleOwnerSelection(message.getEntityPath(), message.getAllCandidates(), strategy);
             }
@@ -347,7 +353,11 @@ class EntityOwnershipShard extends Shard {
                         node(entityTypeNode.getIdentifier()).node(ENTITY_NODE_ID).node(entityNode.getIdentifier()).
                         node(ENTITY_OWNER_NODE_ID).build();
 
-                Object newOwner = newOwner(getCandidateNames(entityNode), getEntityOwnerElectionStrategy(entityPath));
+                String entityType = EntityOwnersModel.entityTypeFromEntityPath(entityPath);
+
+                Object newOwner = newOwner(getCandidateNames(entityNode),
+                        entityOwnershipStatistics.byEntityType(entityType),
+                        getEntityOwnerElectionStrategy(entityPath));
 
                 LOG.debug("{}: Found entity {}, writing new owner {}", persistenceId(), entityPath, newOwner);
 
@@ -462,12 +472,12 @@ class EntityOwnershipShard extends Shard {
         entityToScheduledOwnershipTask.put(entityPath, lastScheduledTask);
     }
 
-    private String newOwner(Collection<String> candidates, EntityOwnerSelectionStrategy ownerSelectionStrategy) {
+    private String newOwner(Collection<String> candidates, Map<String, Long> statistics, EntityOwnerSelectionStrategy ownerSelectionStrategy) {
         Collection<String> viableCandidates = getViableCandidates(candidates);
         if(viableCandidates.size() == 0){
             return "";
         }
-        return ownerSelectionStrategy.newOwner(viableCandidates);
+        return ownerSelectionStrategy.newOwner(viableCandidates, statistics);
     }
 
     private Collection<String> getViableCandidates(Collection<String> candidates) {
index 3ea4362ac9b2a08190ea368a7e6b4e24c55cafbd..02bd00bfb1e43a7dfcd01c7fb98f85f64529f387 100644 (file)
@@ -65,7 +65,10 @@ class EntityOwnershipStatistics extends AbstractEntityOwnerChangeListener {
     }
 
     Map<String, Long> byEntityType(String entityType){
-        return statistics.get(entityType).readOnlySnapshot();
+        if(statistics.get(entityType) != null) {
+            return statistics.get(entityType).readOnlySnapshot();
+        }
+        return new HashMap<>();
     }
 
     private void updateStatistics(String entityType, String candidateName, long count){
index e86c3a11513a6b2a83974b330bcd6baef8c96bcd..53b35f65c404c81aabce94fcd0b83406c8258ee3 100644 (file)
@@ -9,6 +9,7 @@
 package org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy;
 
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * An EntityOwnerSelectionStrategy is to be used by the EntityOwnershipShard to select a new owner from a collection
@@ -25,7 +26,9 @@ public interface EntityOwnerSelectionStrategy {
     /**
      *
      * @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);
+    String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics);
 }
index b009c3aff429e29693a7de30abdbb427f92652b2..d86fcbd24967b59282bb3788c5ad53a6f6dbaa3d 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.datastore.entityownership.selections
 
 import com.google.common.base.Preconditions;
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * The FirstCandidateSelectionStrategy always selects the first viable candidate from the list of candidates
@@ -23,7 +24,7 @@ public class FirstCandidateSelectionStrategy extends AbstractEntityOwnerSelectio
     }
 
     @Override
-    public String newOwner(Collection<String> viableCandidates) {
+    public String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics) {
         Preconditions.checkArgument(viableCandidates.size() > 0, "No viable candidates provided");
         return viableCandidates.iterator().next();
     }
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LeastLoadedCandidateSelectionStrategy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LeastLoadedCandidateSelectionStrategy.java
new file mode 100644 (file)
index 0000000..9ebf21c
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * The LeastLoadedCandidateSelectionStrategy assigns ownership for an entity to the candidate which owns the least
+ * number of entities.
+ */
+public class LeastLoadedCandidateSelectionStrategy extends AbstractEntityOwnerSelectionStrategy {
+    protected LeastLoadedCandidateSelectionStrategy(long selectionDelayInMillis) {
+        super(selectionDelayInMillis);
+    }
+
+    @Override
+    public String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics) {
+        String leastLoadedCandidate = null;
+        long leastLoadedCount = Long.MAX_VALUE;
+
+        for(String candidateName : viableCandidates){
+            Long val = statistics.get(candidateName);
+            if(val != null && val < leastLoadedCount){
+                leastLoadedCount = val;
+                leastLoadedCandidate = candidateName;
+            }
+        }
+
+        if(leastLoadedCandidate == null){
+            return viableCandidates.iterator().next();
+        }
+        return leastLoadedCandidate;
+    }
+}
index 30c627735730d668b600015b369e606d74c9d2c9..ea7deb569331154be02221fd25c7dadffd692480 100644 (file)
@@ -11,6 +11,7 @@ package org.opendaylight.controller.cluster.datastore.entityownership.selections
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 public class LastCandidateSelectionStrategy extends AbstractEntityOwnerSelectionStrategy {
     public LastCandidateSelectionStrategy(long selectionDelayInMillis) {
@@ -18,7 +19,7 @@ public class LastCandidateSelectionStrategy extends AbstractEntityOwnerSelection
     }
 
     @Override
-    public String newOwner(Collection<String> viableCandidates) {
+    public String newOwner(Collection<String> viableCandidates, Map<String, Long> statistics) {
         List<String> candidates = new ArrayList<>(viableCandidates);
         return candidates.get(candidates.size()-1);
     }
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LeastLoadedCandidateSelectionStrategyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/LeastLoadedCandidateSelectionStrategyTest.java
new file mode 100644 (file)
index 0000000..50db591
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy;
+
+import static org.junit.Assert.assertEquals;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+
+public class LeastLoadedCandidateSelectionStrategyTest {
+
+    @Test
+    public void testLeastLoadedStrategy(){
+        LeastLoadedCandidateSelectionStrategy strategy = new LeastLoadedCandidateSelectionStrategy(0L);
+
+        String owner = strategy.newOwner(prepareViableCandidates(3), new HashMap<String, Long>());
+        assertEquals("member-1", owner);
+
+        // member-2 has least load
+        owner = strategy.newOwner(prepareViableCandidates(3), prepareStatistics(5,2,4));
+        assertEquals("member-2", owner);
+
+        // member-3 has least load
+        owner = strategy.newOwner(prepareViableCandidates(3), prepareStatistics(5,7,4));
+        assertEquals("member-3", owner);
+
+        // member-1 has least load
+        owner = strategy.newOwner(prepareViableCandidates(3), prepareStatistics(1,7,4));
+        assertEquals("member-1", owner);
+
+    }
+
+    private Map<String, Long> prepareStatistics(long... count){
+        Map<String, Long> statistics = new HashMap<>();
+        for(int i=0;i<count.length;i++){
+            statistics.put("member-" + (i+1), count[i]);
+        }
+        return statistics;
+    }
+
+    private Collection<String> prepareViableCandidates(int count){
+        Collection<String> viableCandidates = new ArrayList<>();
+        for(int i=0;i<count;i++){
+            viableCandidates.add("member-" + (i+1));
+        }
+        return viableCandidates;
+    }
+}
\ No newline at end of file