From: Moiz Raja Date: Tue, 6 Oct 2015 02:23:42 +0000 (-0700) Subject: Introduce EntityOwnerSelectionStrategy X-Git-Tag: release/beryllium~182 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=4b2622df4d579e9b1be0b25603e002ce58db4463 Introduce EntityOwnerSelectionStrategy 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 --- diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnersModel.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnersModel.java index 404d9b9666..0be765000f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnersModel.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnersModel.java @@ -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; diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnershipShard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnershipShard.java index 3d48271c54..3e5783b62b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnershipShard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/EntityOwnershipShard.java @@ -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 downPeerMemberNames = new HashSet<>(); private final Map peerIdToMemberNames = new HashMap<>(); + private final Map 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> 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 candidates) { - for(String candidate: candidates) { - if(!downPeerMemberNames.contains(candidate)) { - return candidate; - } + private String newOwner(Collection candidates, EntityOwnerSelectionStrategy ownerSelectionStrategy) { + Collection viableCandidates = getViableCandidates(candidates); + if(viableCandidates.size() == 0){ + return ""; } + return ownerSelectionStrategy.newOwner(viableCandidates); + } - return ""; + private Collection getViableCandidates(Collection candidates) { + Collection 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 index 0000000000..dffbac5fbd --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/EntityOwnerSelectionStrategy.java @@ -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 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 index 0000000000..a619035f35 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/entityownership/selectionstrategy/FirstCandidateSelectionStrategy.java @@ -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 viableCandidates) { + Preconditions.checkArgument(viableCandidates.size() > 0, "No viable candidates provided"); + return viableCandidates.iterator().next(); + } +}