Introduce EntityOwnerSelectionStrategy 22/28222/4
authorMoiz Raja <moraja@cisco.com>
Tue, 6 Oct 2015 02:23:42 +0000 (19:23 -0700)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 11 Nov 2015 01:52:31 +0000 (01:52 +0000)
Currently the EntityOwnershipService does not do any load
balancing, in that it allows the first candidate that registers
to become an owner. There is a need to do that so that applications
which choose to do some *work* based on if it owns an entity can
scale better.

This patch introduces the concept of an EntityOwnerSelectionStrategy
with the intent to provide custom strategies later to choose an owner.

Since custom strategies require intimate knowledge of how the
EntityOwnershipShard chooses a leader at this time I do not think
a strategy can be passed to the EntityOwnershipService via API. The
intent therefor is to choose a strategy based on configuration
wherein a custom strategy can be chosen for each entity type. If
the Strategy needs any custom configuration then it can have configuration
files of it's own

Change-Id: Ia53b8edb59fb1d06a426d9d9a95c07ef4ae65cd1
Signed-off-by: Moiz Raja <moraja@cisco.com>
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnersModel.java
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/selectionstrategy/EntityOwnerSelectionStrategy.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/FirstCandidateSelectionStrategy.java [new file with mode: 0644]

index 404d9b9666d0739865a941e585e4e9914f69f80a..0be765000f9c295951efb03da1078b0cd87797b7 100644 (file)
@@ -111,6 +111,18 @@ final class EntityOwnersModel {
                 ImmutableNodes.leafNode(ENTITY_OWNER_QNAME, owner)).build();
     }
 
+    static String entityTypeFromEntityPath(YangInstanceIdentifier entityPath){
+        YangInstanceIdentifier parent = entityPath;
+        while(!parent.isEmpty()) {
+            if (ENTITY_TYPE_QNAME.equals(parent.getLastPathArgument().getNodeType())) {
+                YangInstanceIdentifier.NodeIdentifierWithPredicates entityTypeLastPathArgument = (YangInstanceIdentifier.NodeIdentifierWithPredicates) parent.getLastPathArgument();
+                return (String) entityTypeLastPathArgument.getKeyValues().get(ENTITY_TYPE_QNAME);
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
     static Entity createEntity(YangInstanceIdentifier entityPath) {
         String entityType = null;
         YangInstanceIdentifier entityId = null;
index 3d48271c54f9178685260c30841d1437caa4a62e..3e5783b62b2ea426c6fe912c2331596d3d8f9ab6 100644 (file)
@@ -44,6 +44,8 @@ import org.opendaylight.controller.cluster.datastore.entityownership.messages.Re
 import org.opendaylight.controller.cluster.datastore.entityownership.messages.RegisterListenerLocal;
 import org.opendaylight.controller.cluster.datastore.entityownership.messages.UnregisterCandidateLocal;
 import org.opendaylight.controller.cluster.datastore.entityownership.messages.UnregisterListenerLocal;
+import org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy.EntityOwnerSelectionStrategy;
+import org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy.FirstCandidateSelectionStrategy;
 import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier;
 import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications;
 import org.opendaylight.controller.cluster.datastore.messages.PeerDown;
@@ -69,11 +71,16 @@ import scala.concurrent.Future;
  * @author Thomas Pantelis
  */
 class EntityOwnershipShard extends Shard {
+
+    private static final EntityOwnerSelectionStrategy DEFAULT_ENTITY_OWNER_SELECTION_STRATEGY
+            = FirstCandidateSelectionStrategy.INSTANCE;
+
     private final String localMemberName;
     private final EntityOwnershipShardCommitCoordinator commitCoordinator;
     private final EntityOwnershipListenerSupport listenerSupport;
     private final Set<String> downPeerMemberNames = new HashSet<>();
     private final Map<String, String> peerIdToMemberNames = new HashMap<>();
+    private final Map<String, EntityOwnerSelectionStrategy> ownerSelectionStrategies = new HashMap<>();
 
     private static DatastoreContext noPersistenceDatastoreContext(DatastoreContext datastoreContext) {
         return DatastoreContext.newBuilderFrom(datastoreContext).persistent(false).build();
@@ -165,7 +172,7 @@ class EntityOwnershipShard extends Shard {
                 Optional<DataContainerChild<? extends PathArgument, ?>> possibleType =
                         entityTypeNode.getChild(ENTITY_TYPE_NODE_ID);
                 String entityType = possibleType.isPresent() ? possibleType.get().getValue().toString() : null;
-                if(registerListener.getEntityType().equals(entityType)) {
+                if (registerListener.getEntityType().equals(entityType)) {
                     Entity entity = new Entity(entityType,
                             (YangInstanceIdentifier) entityNode.getChild(ENTITY_ID_NODE_ID).get().getValue());
                     listenerSupport.notifyEntityOwnershipListener(entity, false, true, true, registerListener.getListener());
@@ -245,7 +252,8 @@ class EntityOwnershipShard extends Shard {
         if(isLeader()) {
             String currentOwner = getCurrentOwner(message.getEntityPath());
             if(message.getRemovedCandidate().equals(currentOwner)){
-                writeNewOwner(message.getEntityPath(), newOwner(message.getRemainingCandidates()));
+                writeNewOwner(message.getEntityPath(), newOwner(message.getRemainingCandidates(),
+                        getEntityOwnerElectionStrategy(message.getEntityPath())));
             }
         } else {
             // We're not the leader. If the removed candidate is our local member then check if we actually
@@ -265,6 +273,18 @@ class EntityOwnershipShard extends Shard {
         }
     }
 
+    private EntityOwnerSelectionStrategy getEntityOwnerElectionStrategy(YangInstanceIdentifier entityPath) {
+        String entityType = EntityOwnersModel.entityTypeFromEntityPath(entityPath);
+        EntityOwnerSelectionStrategy entityOwnerSelectionStrategy = ownerSelectionStrategies.get(entityType);
+
+        if(entityOwnerSelectionStrategy == null){
+            entityOwnerSelectionStrategy = DEFAULT_ENTITY_OWNER_SELECTION_STRATEGY;
+            ownerSelectionStrategies.put(entityType, entityOwnerSelectionStrategy);
+        }
+
+        return entityOwnerSelectionStrategy;
+    }
+
     private void onCandidateAdded(CandidateAdded message) {
         if(!isLeader()){
             return;
@@ -278,7 +298,14 @@ class EntityOwnershipShard extends Shard {
 
         String currentOwner = getCurrentOwner(message.getEntityPath());
         if(Strings.isNullOrEmpty(currentOwner)){
-            writeNewOwner(message.getEntityPath(), newOwner(message.getAllCandidates()));
+            EntityOwnerSelectionStrategy entityOwnerSelectionStrategy
+                    = getEntityOwnerElectionStrategy(message.getEntityPath());
+            if(entityOwnerSelectionStrategy.selectionDelayInMillis() == 0L) {
+                writeNewOwner(message.getEntityPath(), newOwner(message.getAllCandidates(),
+                        entityOwnerSelectionStrategy));
+            } else {
+                throw new UnsupportedOperationException("Delayed selection not implemented yet");
+            }
         }
     }
 
@@ -304,11 +331,13 @@ class EntityOwnershipShard extends Shard {
         searchForEntitiesOwnedBy(owner, new EntityWalker() {
             @Override
             public void onEntity(MapEntryNode entityTypeNode, MapEntryNode entityNode) {
-                Object newOwner = newOwner(getCandidateNames(entityNode));
+
                 YangInstanceIdentifier entityPath = YangInstanceIdentifier.builder(ENTITY_TYPES_PATH).
                         node(entityTypeNode.getIdentifier()).node(ENTITY_NODE_ID).node(entityNode.getIdentifier()).
                         node(ENTITY_OWNER_NODE_ID).build();
 
+                Object newOwner = newOwner(getCandidateNames(entityNode), getEntityOwnerElectionStrategy(entityPath));
+
                 LOG.debug("{}: Found entity {}, writing new owner {}", persistenceId(), entityPath, newOwner);
 
                 modifications.addModification(new WriteModification(entityPath,
@@ -402,14 +431,23 @@ class EntityOwnershipShard extends Shard {
                 ImmutableNodes.leafNode(ENTITY_OWNER_NODE_ID, newOwner)), this);
     }
 
-    private String newOwner(Collection<String> candidates) {
-        for(String candidate: candidates) {
-            if(!downPeerMemberNames.contains(candidate)) {
-                return candidate;
-            }
+    private String newOwner(Collection<String> candidates, EntityOwnerSelectionStrategy ownerSelectionStrategy) {
+        Collection<String> viableCandidates = getViableCandidates(candidates);
+        if(viableCandidates.size() == 0){
+            return "";
         }
+        return ownerSelectionStrategy.newOwner(viableCandidates);
+    }
 
-        return "";
+    private Collection<String> getViableCandidates(Collection<String> candidates) {
+        Collection<String> viableCandidates = new ArrayList<>();
+
+        for (String candidate : candidates) {
+            if (!downPeerMemberNames.contains(candidate)) {
+                viableCandidates.add(candidate);
+            }
+        }
+        return viableCandidates;
     }
 
     private String getCurrentOwner(YangInstanceIdentifier entityId) {
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/EntityOwnerSelectionStrategy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/EntityOwnerSelectionStrategy.java
new file mode 100644 (file)
index 0000000..dffbac5
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * An EntityOwnerSelectionStrategy is to be used by the EntityOwnershipShard to select a new owner from a collection
+ * of candidates
+ */
+public interface EntityOwnerSelectionStrategy {
+    /**
+     *
+     * @return the time in millis owner selection should be delayed
+     */
+    long selectionDelayInMillis();
+
+
+    /**
+     *
+     * @param viableCandidates the available candidates from which to choose the new owner
+     * @return the new owner
+     */
+    String newOwner(Collection<String> viableCandidates);
+}
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/FirstCandidateSelectionStrategy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/FirstCandidateSelectionStrategy.java
new file mode 100644 (file)
index 0000000..a619035
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+import java.util.Collection;
+
+/**
+ * The FirstCandidateSelectionStrategy always selects the first viable candidate from the list of candidates
+ */
+public class FirstCandidateSelectionStrategy implements EntityOwnerSelectionStrategy {
+
+    public static final FirstCandidateSelectionStrategy INSTANCE = new FirstCandidateSelectionStrategy();
+
+    @Override
+    public long selectionDelayInMillis() {
+        return 0;
+    }
+
+    @Override
+    public String newOwner(Collection<String> viableCandidates) {
+        Preconditions.checkArgument(viableCandidates.size() > 0, "No viable candidates provided");
+        return viableCandidates.iterator().next();
+    }
+}