<artifactId>sal-distributed-datastore</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sal-distributed-eos</artifactId>
- <version>${project.version}</version>
- </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sal-remoterpc-connector</artifactId>
<module>sal-dummy-distributed-datastore</module>
<module>sal-cluster-admin-api</module>
<module>sal-cluster-admin-impl</module>
- <module>sal-distributed-eos</module>
+
+ <!-- Entity Ownership Service on top of Akka Distributed Data/Singleton -->
<module>eos-dom-akka</module>
<!-- Yang Test Models for MD-SAL -->
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>mdsal-parent</artifactId>
- <version>4.0.0-SNAPSHOT</version>
- <relativePath>../parent</relativePath>
- </parent>
-
- <artifactId>sal-distributed-eos</artifactId>
- <packaging>bundle</packaging>
-
- <dependencies>
- <dependency>
- <groupId>org.opendaylight.mdsal</groupId>
- <artifactId>mdsal-eos-dom-api</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>osgi.cmpn</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-distributed-datastore</artifactId>
- </dependency>
-
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.typesafe.akka</groupId>
- <artifactId>akka-testkit_2.13</artifactId>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-akka-raft-example</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-akka-raft</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-distributed-datastore</artifactId>
- <version>${project.version}</version>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-test-util</artifactId>
- </dependency>
- </dependencies>
-
- <scm>
- <connection>scm:git:http://git.opendaylight.org/gerrit/controller.git</connection>
- <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
- <tag>HEAD</tag>
- <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Architecture:Clustering</url>
- </scm>
-</project>
+++ /dev/null
-/*
- * 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.entityownership;
-
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNER_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_QNAME;
-
-import java.util.Optional;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.EntityType;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
-
-public abstract class AbstractEntityOwnerChangeListener implements DOMDataTreeChangeListener {
- private static final YangInstanceIdentifier EOS_PATH = YangInstanceIdentifier.builder(ENTITY_OWNERS_PATH)
- .node(EntityType.QNAME).node(EntityType.QNAME).node(ENTITY_QNAME).node(ENTITY_QNAME)
- .node(ENTITY_OWNER_QNAME).build();
-
- void init(final ShardDataTree shardDataTree) {
- shardDataTree.registerTreeChangeListener(EOS_PATH, this, Optional.empty(), noop -> { /* NOOP */ });
- }
-
- protected static String extractOwner(final LeafNode<?> ownerLeaf) {
- return ownerLeaf.body().toString();
- }
-}
+++ /dev/null
-/*
- * 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.entityownership;
-
-import static java.util.Objects.requireNonNull;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.CANDIDATE_NAME_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_ID_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_QNAME;
-
-import akka.actor.ActorRef;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateAdded;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateRemoved;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.EntityType;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.entity.type.entity.Candidate;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Listens for candidate entries added/removed and notifies the EntityOwnershipShard appropriately.
- *
- * @author Moiz Raja
- * @author Thomas Pantelis
- */
-class CandidateListChangeListener implements DOMDataTreeChangeListener {
- private static final Logger LOG = LoggerFactory.getLogger(CandidateListChangeListener.class);
-
- private final String logId;
- private final ActorRef shard;
- private final Map<YangInstanceIdentifier, Collection<String>> currentCandidates = new HashMap<>();
-
- CandidateListChangeListener(final ActorRef shard, final String logId) {
- this.shard = requireNonNull(shard, "shard should not be null");
- this.logId = logId;
- }
-
- void init(final ShardDataTree shardDataTree) {
- shardDataTree.registerTreeChangeListener(YangInstanceIdentifier.builder(ENTITY_OWNERS_PATH)
- .node(EntityType.QNAME).node(EntityType.QNAME).node(ENTITY_QNAME).node(ENTITY_QNAME)
- .node(Candidate.QNAME).node(Candidate.QNAME).build(), this, Optional.empty(), noop -> { /* NOOP */ });
- }
-
- @Override
- public void onInitialData() {
- // No-op
- }
-
- @Override
- public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
- for (DataTreeCandidate change: changes) {
- DataTreeCandidateNode changeRoot = change.getRootNode();
- ModificationType type = changeRoot.getModificationType();
-
- LOG.debug("{}: Candidate node changed: {}, {}", logId, type, change.getRootPath());
-
- NodeIdentifierWithPredicates candidateKey =
- (NodeIdentifierWithPredicates) change.getRootPath().getLastPathArgument();
- String candidate = candidateKey.getValue(CANDIDATE_NAME_QNAME).toString();
-
- YangInstanceIdentifier entityId = extractEntityPath(change.getRootPath());
-
- if (type == ModificationType.WRITE || type == ModificationType.APPEARED) {
- LOG.debug("{}: Candidate {} was added for entity {}", logId, candidate, entityId);
-
- Collection<String> newCandidates = addToCurrentCandidates(entityId, candidate);
- shard.tell(new CandidateAdded(entityId, candidate, new ArrayList<>(newCandidates)), shard);
- } else if (type == ModificationType.DELETE || type == ModificationType.DISAPPEARED) {
- LOG.debug("{}: Candidate {} was removed for entity {}", logId, candidate, entityId);
-
- Collection<String> newCandidates = removeFromCurrentCandidates(entityId, candidate);
- shard.tell(new CandidateRemoved(entityId, candidate, new ArrayList<>(newCandidates)), shard);
- }
- }
- }
-
- private Collection<String> addToCurrentCandidates(final YangInstanceIdentifier entityId,
- final String newCandidate) {
- Collection<String> candidates = currentCandidates.computeIfAbsent(entityId, k -> new LinkedHashSet<>());
- candidates.add(newCandidate);
- return candidates;
- }
-
- private Collection<String> removeFromCurrentCandidates(final YangInstanceIdentifier entityId,
- final String candidateToRemove) {
- Collection<String> candidates = currentCandidates.get(entityId);
- if (candidates != null) {
- candidates.remove(candidateToRemove);
- return candidates;
- }
-
- // Shouldn't happen
- return Collections.emptyList();
- }
-
- private static YangInstanceIdentifier extractEntityPath(final YangInstanceIdentifier candidatePath) {
- List<PathArgument> newPathArgs = new ArrayList<>();
- for (PathArgument pathArg: candidatePath.getPathArguments()) {
- newPathArgs.add(pathArg);
- if (pathArg instanceof NodeIdentifierWithPredicates
- && ENTITY_ID_QNAME.equals(((NodeIdentifierWithPredicates) pathArg).keySet().iterator().next())) {
- break;
- }
- }
-
- return YangInstanceIdentifier.create(newPathArgs);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
-import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
-
-/**
- * Implementation of EntityOwnershipCandidateRegistration.
- *
- * @author Thomas Pantelis
- */
-class DistributedEntityOwnershipCandidateRegistration extends AbstractObjectRegistration<DOMEntity>
- implements DOMEntityOwnershipCandidateRegistration {
- private final DistributedEntityOwnershipService service;
-
- DistributedEntityOwnershipCandidateRegistration(final DOMEntity entity,
- final DistributedEntityOwnershipService service) {
- super(entity);
- this.service = service;
- }
-
- @Override
- protected void removeRegistration() {
- service.unregisterCandidate(getInstance());
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
-import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
-
-/**
- * Implementation of EntityOwnershipListenerRegistration.
- *
- * @author Thomas Pantelis
- */
-class DistributedEntityOwnershipListenerRegistration extends AbstractObjectRegistration<DOMEntityOwnershipListener>
- implements DOMEntityOwnershipListenerRegistration {
- private final DistributedEntityOwnershipService service;
- private final String entityType;
-
- DistributedEntityOwnershipListenerRegistration(final DOMEntityOwnershipListener listener, final String entityType,
- final DistributedEntityOwnershipService service) {
- super(listener);
- this.entityType = requireNonNull(entityType, "entityType cannot be null");
- this.service = requireNonNull(service, "DOMEntityOwnershipListener cannot be null");
- }
-
- @Override
- protected void removeRegistration() {
- service.unregisterListener(getEntityType(), getInstance());
- }
-
- @Override
- public String getEntityType() {
- return entityType;
- }
-
- @Override
- protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
- return toStringHelper.add("entityType", entityType);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static java.util.Objects.requireNonNull;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.CANDIDATE_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNER_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-
-import akka.actor.ActorRef;
-import akka.dispatch.OnComplete;
-import akka.pattern.Patterns;
-import akka.util.Timeout;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.util.Collection;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.datastore.config.Configuration;
-import org.opendaylight.controller.cluster.datastore.config.ModuleShardConfiguration;
-import org.opendaylight.controller.cluster.datastore.messages.CreateShard;
-import org.opendaylight.controller.cluster.datastore.messages.GetShardDataTree;
-import org.opendaylight.controller.cluster.datastore.shardstrategy.ModuleShardStrategy;
-import org.opendaylight.controller.cluster.datastore.utils.ActorUtils;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig;
-import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
-import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.EntityOwners;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import scala.concurrent.Await;
-import scala.concurrent.Future;
-import scala.concurrent.duration.Duration;
-
-/**
- * The distributed implementation of the EntityOwnershipService.
- *
- * @author Thomas Pantelis
- */
-public class DistributedEntityOwnershipService implements DOMEntityOwnershipService, AutoCloseable {
- @VisibleForTesting
- static final String ENTITY_OWNERSHIP_SHARD_NAME = "entity-ownership";
-
- private static final Logger LOG = LoggerFactory.getLogger(DistributedEntityOwnershipService.class);
- private static final Timeout MESSAGE_TIMEOUT = new Timeout(1, TimeUnit.MINUTES);
-
- private final ConcurrentMap<DOMEntity, DOMEntity> registeredEntities = new ConcurrentHashMap<>();
- private final ActorUtils context;
-
- private volatile ActorRef localEntityOwnershipShard;
- private volatile DataTree localEntityOwnershipShardDataTree;
-
- DistributedEntityOwnershipService(final ActorUtils context) {
- this.context = requireNonNull(context);
- }
-
- public static DistributedEntityOwnershipService start(final ActorUtils context,
- final EntityOwnerSelectionStrategyConfig strategyConfig) {
- ActorRef shardManagerActor = context.getShardManager();
-
- Configuration configuration = context.getConfiguration();
- Collection<MemberName> entityOwnersMemberNames = configuration.getUniqueMemberNamesForAllShards();
- CreateShard createShard = new CreateShard(new ModuleShardConfiguration(EntityOwners.QNAME.getNamespace(),
- "entity-owners", ENTITY_OWNERSHIP_SHARD_NAME, ModuleShardStrategy.NAME, entityOwnersMemberNames),
- newShardBuilder(context, strategyConfig), null);
-
- Future<Object> createFuture = context.executeOperationAsync(shardManagerActor, createShard, MESSAGE_TIMEOUT);
- createFuture.onComplete(new OnComplete<>() {
- @Override
- public void onComplete(final Throwable failure, final Object response) {
- if (failure != null) {
- LOG.error("Failed to create {} shard", ENTITY_OWNERSHIP_SHARD_NAME, failure);
- } else {
- LOG.info("Successfully created {} shard", ENTITY_OWNERSHIP_SHARD_NAME);
- }
- }
- }, context.getClientDispatcher());
-
- return new DistributedEntityOwnershipService(context);
- }
-
- private void executeEntityOwnershipShardOperation(final ActorRef shardActor, final Object message) {
- Future<Object> future = context.executeOperationAsync(shardActor, message, MESSAGE_TIMEOUT);
- future.onComplete(new OnComplete<>() {
- @Override
- public void onComplete(final Throwable failure, final Object response) {
- if (failure != null) {
- // FIXME: CONTROLLER-1904: reduce the severity to info once we have a retry mechanism
- LOG.error("Error sending message {} to {}", message, shardActor, failure);
- } else {
- LOG.debug("{} message to {} succeeded", message, shardActor);
- }
- }
- }, context.getClientDispatcher());
- }
-
- @VisibleForTesting
- void executeLocalEntityOwnershipShardOperation(final Object message) {
- if (localEntityOwnershipShard == null) {
- Future<ActorRef> future = context.findLocalShardAsync(ENTITY_OWNERSHIP_SHARD_NAME);
- future.onComplete(new OnComplete<ActorRef>() {
- @Override
- public void onComplete(final Throwable failure, final ActorRef shardActor) {
- if (failure != null) {
- // FIXME: CONTROLLER-1904: reduce the severity to info once we have a retry mechanism
- LOG.error("Failed to find local {} shard", ENTITY_OWNERSHIP_SHARD_NAME, failure);
- } else {
- localEntityOwnershipShard = shardActor;
- executeEntityOwnershipShardOperation(localEntityOwnershipShard, message);
- }
- }
- }, context.getClientDispatcher());
-
- } else {
- executeEntityOwnershipShardOperation(localEntityOwnershipShard, message);
- }
- }
-
- @Override
- public DOMEntityOwnershipCandidateRegistration registerCandidate(final DOMEntity entity)
- throws CandidateAlreadyRegisteredException {
- requireNonNull(entity, "entity cannot be null");
-
- if (registeredEntities.putIfAbsent(entity, entity) != null) {
- throw new CandidateAlreadyRegisteredException(entity);
- }
-
- RegisterCandidateLocal registerCandidate = new RegisterCandidateLocal(entity);
-
- LOG.debug("Registering candidate with message: {}", registerCandidate);
-
- executeLocalEntityOwnershipShardOperation(registerCandidate);
- return new DistributedEntityOwnershipCandidateRegistration(entity, this);
- }
-
- void unregisterCandidate(final DOMEntity entity) {
- LOG.debug("Unregistering candidate for {}", entity);
-
- executeLocalEntityOwnershipShardOperation(new UnregisterCandidateLocal(entity));
- registeredEntities.remove(entity);
- }
-
- @Override
- public DOMEntityOwnershipListenerRegistration registerListener(final String entityType,
- final DOMEntityOwnershipListener listener) {
- RegisterListenerLocal registerListener = new RegisterListenerLocal(listener, entityType);
-
- LOG.debug("Registering listener with message: {}", registerListener);
-
- executeLocalEntityOwnershipShardOperation(registerListener);
- return new DistributedEntityOwnershipListenerRegistration(listener, entityType, this);
- }
-
- @Override
- public Optional<EntityOwnershipState> getOwnershipState(final DOMEntity forEntity) {
- requireNonNull(forEntity, "forEntity cannot be null");
-
- DataTree dataTree = getLocalEntityOwnershipShardDataTree();
- if (dataTree == null) {
- return Optional.empty();
- }
-
- Optional<NormalizedNode> entityNode = dataTree.takeSnapshot().readNode(
- entityPath(forEntity.getType(), forEntity.getIdentifier()));
- if (!entityNode.isPresent()) {
- return Optional.empty();
- }
-
- // Check if there are any candidates, if there are none we do not really have ownership state
- final MapEntryNode entity = (MapEntryNode) entityNode.get();
- final Optional<DataContainerChild> optionalCandidates = entity.findChildByArg(CANDIDATE_NODE_ID);
- final boolean hasCandidates = optionalCandidates.isPresent()
- && ((MapNode) optionalCandidates.get()).body().size() > 0;
- if (!hasCandidates) {
- return Optional.empty();
- }
-
- MemberName localMemberName = context.getCurrentMemberName();
- Optional<DataContainerChild> ownerLeaf = entity.findChildByArg(ENTITY_OWNER_NODE_ID);
- String owner = ownerLeaf.isPresent() ? ownerLeaf.get().body().toString() : null;
- boolean hasOwner = !Strings.isNullOrEmpty(owner);
- boolean isOwner = hasOwner && localMemberName.getName().equals(owner);
-
- return Optional.of(EntityOwnershipState.from(isOwner, hasOwner));
- }
-
- @Override
- public boolean isCandidateRegistered(final DOMEntity entity) {
- return registeredEntities.get(entity) != null;
- }
-
- @VisibleForTesting
- @SuppressWarnings("checkstyle:IllegalCatch")
- @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Akka's Await.result() API contract")
- DataTree getLocalEntityOwnershipShardDataTree() {
- final DataTree local = localEntityOwnershipShardDataTree;
- if (local != null) {
- return local;
- }
-
- if (localEntityOwnershipShard == null) {
- try {
- localEntityOwnershipShard = Await.result(context.findLocalShardAsync(
- ENTITY_OWNERSHIP_SHARD_NAME), Duration.Inf());
- } catch (TimeoutException | InterruptedException e) {
- LOG.error("Failed to find local {} shard", ENTITY_OWNERSHIP_SHARD_NAME, e);
- return null;
- }
- }
-
- try {
- return localEntityOwnershipShardDataTree = (DataTree) Await.result(Patterns.ask(localEntityOwnershipShard,
- GetShardDataTree.INSTANCE, MESSAGE_TIMEOUT), Duration.Inf());
- } catch (TimeoutException | InterruptedException e) {
- LOG.error("Failed to find local {} shard", ENTITY_OWNERSHIP_SHARD_NAME, e);
- return null;
- }
- }
-
- void unregisterListener(final String entityType, final DOMEntityOwnershipListener listener) {
- LOG.debug("Unregistering listener {} for entity type {}", listener, entityType);
-
- executeLocalEntityOwnershipShardOperation(new UnregisterListenerLocal(listener, entityType));
- }
-
- @Override
- public void close() {
- }
-
- private static EntityOwnershipShard.Builder newShardBuilder(final ActorUtils context,
- final EntityOwnerSelectionStrategyConfig strategyConfig) {
- return EntityOwnershipShard.newBuilder().localMemberName(context.getCurrentMemberName())
- .ownerSelectionStrategyConfig(strategyConfig);
- }
-
- @VisibleForTesting
- ActorRef getLocalEntityOwnershipShard() {
- return localEntityOwnershipShard;
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static com.google.common.base.Verify.verifyNotNull;
-import static java.util.Objects.requireNonNull;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.createEntity;
-
-import com.google.common.base.Strings;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.Optional;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Listens for entity owner changes and notifies the EntityOwnershipListenerSupport appropriately.
- *
- * @author Thomas Pantelis
- */
-class EntityOwnerChangeListener extends AbstractEntityOwnerChangeListener {
- private static final Logger LOG = LoggerFactory.getLogger(EntityOwnerChangeListener.class);
-
- private final String localMemberName;
- private final EntityOwnershipChangePublisher publisher;
-
- EntityOwnerChangeListener(final MemberName localMemberName, final EntityOwnershipChangePublisher publisher) {
- this.localMemberName = verifyNotNull(localMemberName.getName());
- this.publisher = requireNonNull(publisher);
- }
-
- @Override
- public void onInitialData() {
- // No-op
- }
-
- @Override
- public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
- for (DataTreeCandidate change: changes) {
- DataTreeCandidateNode changeRoot = change.getRootNode();
- LeafNode<?> ownerLeaf = (LeafNode<?>) changeRoot.getDataAfter().get();
-
- LOG.debug("{}: Entity node changed: {}, {}", logId(), changeRoot.getModificationType(),
- change.getRootPath());
-
- String newOwner = extractOwner(ownerLeaf);
-
- String origOwner = null;
- Optional<NormalizedNode> dataBefore = changeRoot.getDataBefore();
- if (dataBefore.isPresent()) {
- origOwner = extractOwner((LeafNode<?>) changeRoot.getDataBefore().get());
- }
-
- LOG.debug("{}: New owner: {}, Original owner: {}", logId(), newOwner, origOwner);
-
- if (!Objects.equals(origOwner, newOwner)) {
- boolean isOwner = localMemberName.equals(newOwner);
- boolean wasOwner = localMemberName.equals(origOwner);
- boolean hasOwner = !Strings.isNullOrEmpty(newOwner);
-
- DOMEntity entity = createEntity(change.getRootPath());
-
- LOG.debug(
- "{}: Calling notifyEntityOwnershipListeners: entity: {}, wasOwner: {}, isOwner: {}, hasOwner: {}",
- logId(), entity, wasOwner, isOwner, hasOwner);
-
- publisher.notifyEntityOwnershipListeners(entity, wasOwner, isOwner, hasOwner);
- }
- }
- }
-
- private String logId() {
- return publisher.getLogId();
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import java.util.Map.Entry;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.EntityOwners;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.EntityType;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.entity.type.Entity;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.entity.type.entity.Candidate;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUserMapNodeBuilder;
-
-/**
- * Utility methods for entity-owners yang model.
- *
- * @author Thomas Pantelis
- */
-public final class EntityOwnersModel {
- static final QName ENTITY_QNAME = Entity.QNAME;
- static final QName CANDIDATE_NAME_QNAME = QName.create(Candidate.QNAME, "name").intern();
- static final QName ENTITY_ID_QNAME = QName.create(ENTITY_QNAME, "id").intern();
- static final QName ENTITY_OWNER_QNAME = QName.create(ENTITY_QNAME, "owner").intern();
- static final QName ENTITY_TYPE_QNAME = QName.create(EntityType.QNAME, "type").intern();
-
- static final NodeIdentifier ENTITY_OWNERS_NODE_ID = NodeIdentifier.create(EntityOwners.QNAME);
- static final NodeIdentifier ENTITY_OWNER_NODE_ID = NodeIdentifier.create(ENTITY_OWNER_QNAME);
- static final NodeIdentifier ENTITY_NODE_ID = NodeIdentifier.create(ENTITY_QNAME);
- static final NodeIdentifier ENTITY_ID_NODE_ID = NodeIdentifier.create(ENTITY_ID_QNAME);
- static final NodeIdentifier ENTITY_TYPE_NODE_ID = NodeIdentifier.create(ENTITY_TYPE_QNAME);
- static final NodeIdentifier CANDIDATE_NODE_ID = NodeIdentifier.create(Candidate.QNAME);
- static final NodeIdentifier CANDIDATE_NAME_NODE_ID = NodeIdentifier.create(CANDIDATE_NAME_QNAME);
- static final YangInstanceIdentifier ENTITY_OWNERS_PATH = YangInstanceIdentifier.create(ENTITY_OWNERS_NODE_ID);
- static final YangInstanceIdentifier ENTITY_TYPES_PATH = ENTITY_OWNERS_PATH.node(EntityType.QNAME).toOptimized();
-
- private EntityOwnersModel() {
- }
-
- static YangInstanceIdentifier entityPath(final String entityType, final YangInstanceIdentifier entityId) {
- return YangInstanceIdentifier.builder(ENTITY_OWNERS_PATH).node(EntityType.QNAME)
- .nodeWithKey(EntityType.QNAME, ENTITY_TYPE_QNAME, entityType).node(ENTITY_QNAME)
- .nodeWithKey(ENTITY_QNAME, ENTITY_ID_QNAME, entityId).build();
-
- }
-
- static YangInstanceIdentifier candidatePath(final String entityType, final YangInstanceIdentifier entityId,
- final String candidateName) {
- return YangInstanceIdentifier.builder(ENTITY_OWNERS_PATH).node(EntityType.QNAME)
- .nodeWithKey(EntityType.QNAME, ENTITY_TYPE_QNAME, entityType).node(ENTITY_QNAME)
- .nodeWithKey(ENTITY_QNAME, ENTITY_ID_QNAME, entityId).node(Candidate.QNAME)
- .nodeWithKey(Candidate.QNAME, CANDIDATE_NAME_QNAME, candidateName).build();
- }
-
- static YangInstanceIdentifier candidatePath(final YangInstanceIdentifier entityPath, final String candidateName) {
- return YangInstanceIdentifier.builder(entityPath).node(Candidate.QNAME).nodeWithKey(
- Candidate.QNAME, CANDIDATE_NAME_QNAME, candidateName).build();
- }
-
- static NodeIdentifierWithPredicates candidateNodeKey(final String candidateName) {
- return NodeIdentifierWithPredicates.of(Candidate.QNAME, CANDIDATE_NAME_QNAME, candidateName);
- }
-
- static NormalizedNode entityOwnersWithCandidate(final String entityType,
- final YangInstanceIdentifier entityId, final String candidateName) {
- return entityOwnersWithEntityTypeEntry(entityTypeEntryWithEntityEntry(entityType,
- entityEntryWithCandidateEntry(entityId, candidateName)));
- }
-
- static ContainerNode entityOwnersWithEntityTypeEntry(final MapEntryNode entityTypeNode) {
- return ImmutableContainerNodeBuilder.create().withNodeIdentifier(
- ENTITY_OWNERS_NODE_ID).addChild(ImmutableNodes.mapNodeBuilder(EntityType.QNAME)
- .addChild(entityTypeNode).build()).build();
- }
-
- static MapEntryNode entityTypeEntryWithEntityEntry(final String entityType, final MapEntryNode entityNode) {
- return ImmutableNodes.mapEntryBuilder(EntityType.QNAME,
- ENTITY_TYPE_QNAME, entityType).addChild(ImmutableNodes.mapNodeBuilder(
- ENTITY_QNAME).addChild(entityNode).build()).build();
- }
-
- static MapEntryNode entityEntryWithCandidateEntry(final YangInstanceIdentifier entityId,
- final String candidateName) {
- return ImmutableNodes.mapEntryBuilder(ENTITY_QNAME, ENTITY_ID_QNAME, entityId).addChild(
- candidateEntry(candidateName)).build();
- }
-
- static MapNode candidateEntry(final String candidateName) {
- return ImmutableUserMapNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(Candidate.QNAME))
- .addChild(candidateMapEntry(candidateName)).build();
- }
-
- static MapEntryNode candidateMapEntry(final String candidateName) {
- return ImmutableNodes.mapEntry(Candidate.QNAME, CANDIDATE_NAME_QNAME, candidateName);
- }
-
- static MapEntryNode entityEntryWithOwner(final YangInstanceIdentifier entityId, final String owner) {
- return ImmutableNodes.mapEntryBuilder(ENTITY_QNAME, ENTITY_ID_QNAME, entityId)
- .addChild(ImmutableNodes.leafNode(ENTITY_OWNER_QNAME, owner != null ? owner : ""))
- .build();
- }
-
- public static String entityTypeFromEntityPath(final YangInstanceIdentifier entityPath) {
- YangInstanceIdentifier parent = entityPath;
- while (!parent.isEmpty()) {
- if (EntityType.QNAME.equals(parent.getLastPathArgument().getNodeType())) {
- YangInstanceIdentifier.NodeIdentifierWithPredicates entityTypeLastPathArgument =
- (YangInstanceIdentifier.NodeIdentifierWithPredicates) parent.getLastPathArgument();
- return (String) entityTypeLastPathArgument.getValue(ENTITY_TYPE_QNAME);
- }
- parent = parent.getParent();
- }
- return null;
- }
-
- static DOMEntity createEntity(final YangInstanceIdentifier entityPath) {
- String entityType = null;
- YangInstanceIdentifier entityId = null;
- for (PathArgument pathArg: entityPath.getPathArguments()) {
- if (pathArg instanceof NodeIdentifierWithPredicates) {
- NodeIdentifierWithPredicates nodeKey = (NodeIdentifierWithPredicates) pathArg;
- Entry<QName, Object> key = nodeKey.entrySet().iterator().next();
- if (ENTITY_TYPE_QNAME.equals(key.getKey())) {
- entityType = key.getValue().toString();
- } else if (ENTITY_ID_QNAME.equals(key.getKey())) {
- entityId = (YangInstanceIdentifier) key.getValue();
- }
- }
- }
-
- return new DOMEntity(entityType, entityId);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2017 Brocade Communications 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.entityownership;
-
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-
-/**
- * Abstract base for notifying EntityOwnershipListeners.
- *
- * @author Thomas Pantelis
- */
-abstract class EntityOwnershipChangePublisher {
- abstract void notifyEntityOwnershipListeners(DOMEntity entity, boolean wasOwner, boolean isOwner, boolean hasOwner);
-
- abstract String getLogId();
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static java.util.Objects.requireNonNull;
-
-import akka.actor.Props;
-import akka.japi.Creator;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import org.opendaylight.controller.cluster.common.actor.AbstractUntypedActor;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-
-/**
- * An actor which is responsible for notifying an EntityOwnershipListener of changes.
- *
- * @author Thomas Pantelis
- */
-final class EntityOwnershipListenerActor extends AbstractUntypedActor {
- private final DOMEntityOwnershipListener listener;
-
- private EntityOwnershipListenerActor(final DOMEntityOwnershipListener listener) {
- this.listener = listener;
- }
-
- @Override
- protected void handleReceive(final Object message) {
- if (message instanceof DOMEntityOwnershipChange) {
- onEntityOwnershipChanged((DOMEntityOwnershipChange)message);
- } else {
- unknownMessage(message);
- }
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- private void onEntityOwnershipChanged(final DOMEntityOwnershipChange change) {
- LOG.debug("Notifying EntityOwnershipListener {}: {}", listener, change);
-
- try {
- listener.ownershipChanged(change);
- } catch (Exception e) {
- LOG.error("Error notifying listener {}", listener, e);
- }
- }
-
- static Props props(final DOMEntityOwnershipListener listener) {
- return Props.create(EntityOwnershipListenerActor.class, new EntityOwnershipListenerCreator(listener));
- }
-
- private static final class EntityOwnershipListenerCreator implements Creator<EntityOwnershipListenerActor> {
- private static final long serialVersionUID = 1L;
-
- @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "This field is not Serializable but we don't "
- + "create remote instances of this actor and thus don't need it to be Serializable.")
- private final DOMEntityOwnershipListener listener;
-
- EntityOwnershipListenerCreator(final DOMEntityOwnershipListener listener) {
- this.listener = requireNonNull(listener);
- }
-
- @Override
- public EntityOwnershipListenerActor create() {
- return new EntityOwnershipListenerActor(listener);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import akka.actor.ActorContext;
-import akka.actor.ActorRef;
-import akka.actor.PoisonPill;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Multimap;
-import java.util.Collection;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.stream.Collectors;
-import org.checkerframework.checker.lock.qual.GuardedBy;
-import org.checkerframework.checker.lock.qual.Holding;
-import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Manages EntityOwnershipListener registrations and notifications for the EntityOwnershipShard. This class is
- * thread-safe.
- *
- * @author Thomas Pantelis
- */
-class EntityOwnershipListenerSupport extends EntityOwnershipChangePublisher {
- private static final Logger LOG = LoggerFactory.getLogger(EntityOwnershipListenerSupport.class);
-
- private final String logId;
- private final ActorContext actorContext;
- private final ReadWriteLock listenerLock = new ReentrantReadWriteLock();
-
- @GuardedBy("listenerLock")
- private final Map<DOMEntityOwnershipListener, ListenerActorRefEntry> listenerActorMap = new IdentityHashMap<>();
-
- @GuardedBy("listenerLock")
- private final Multimap<String, DOMEntityOwnershipListener> entityTypeListenerMap = HashMultimap.create();
-
- private volatile boolean inJeopardy = false;
-
- EntityOwnershipListenerSupport(final ActorContext actorContext, final String logId) {
- this.actorContext = actorContext;
- this.logId = logId;
- }
-
- @Override
- String getLogId() {
- return logId;
- }
-
- /**
- * Set the in-jeopardy flag and indicate its previous state.
- *
- * @param inJeopardy new value of the in-jeopardy flag
- * @return Previous value of the flag.
- */
- @SuppressWarnings("checkstyle:hiddenField")
- boolean setInJeopardy(final boolean inJeopardy) {
- final boolean wasInJeopardy = this.inJeopardy;
- this.inJeopardy = inJeopardy;
- return wasInJeopardy;
- }
-
- void addEntityOwnershipListener(final String entityType, final DOMEntityOwnershipListener listener) {
- LOG.debug("{}: Adding EntityOwnershipListener {} for entity type {}", logId, listener, entityType);
-
- listenerLock.writeLock().lock();
- try {
- if (entityTypeListenerMap.put(entityType, listener)) {
- ListenerActorRefEntry listenerEntry = listenerActorMap.get(listener);
- if (listenerEntry == null) {
- listenerActorMap.put(listener, new ListenerActorRefEntry(listener));
- } else {
- listenerEntry.referenceCount++;
- }
- }
- } finally {
- listenerLock.writeLock().unlock();
- }
- }
-
- void removeEntityOwnershipListener(final String entityType, final DOMEntityOwnershipListener listener) {
- LOG.debug("{}: Removing EntityOwnershipListener {} for entity type {}", logId, listener, entityType);
-
- listenerLock.writeLock().lock();
- try {
- if (entityTypeListenerMap.remove(entityType, listener)) {
- ListenerActorRefEntry listenerEntry = listenerActorMap.get(listener);
-
- LOG.debug("{}: Found {}", logId, listenerEntry);
-
- listenerEntry.referenceCount--;
- if (listenerEntry.referenceCount <= 0) {
- listenerActorMap.remove(listener);
-
- if (listenerEntry.actorRef != null) {
- LOG.debug("Killing EntityOwnershipListenerActor {}", listenerEntry.actorRef);
- listenerEntry.actorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());
- }
- }
- }
- } finally {
- listenerLock.writeLock().unlock();
- }
- }
-
- @Override
- void notifyEntityOwnershipListeners(final DOMEntity entity, final boolean wasOwner, final boolean isOwner,
- final boolean hasOwner) {
- listenerLock.readLock().lock();
- try {
- Collection<DOMEntityOwnershipListener> listeners = entityTypeListenerMap.get(entity.getType());
- if (!listeners.isEmpty()) {
- notifyListeners(entity, wasOwner, isOwner, hasOwner,
- listeners.stream().map(listenerActorMap::get).collect(Collectors.toList()));
- }
- } finally {
- listenerLock.readLock().unlock();
- }
- }
-
- void notifyEntityOwnershipListener(final DOMEntity entity, final boolean wasOwner, final boolean isOwner,
- final boolean hasOwner, final DOMEntityOwnershipListener listener) {
- listenerLock.readLock().lock();
- try {
- notifyListeners(entity, wasOwner, isOwner, hasOwner, ImmutableList.of(listenerActorMap.get(listener)));
- } finally {
- listenerLock.readLock().unlock();
- }
- }
-
- @Holding("listenerLock")
- private void notifyListeners(final DOMEntity entity, final boolean wasOwner, final boolean isOwner,
- final boolean hasOwner, final Collection<ListenerActorRefEntry> listenerEntries) {
- DOMEntityOwnershipChange changed = new DOMEntityOwnershipChange(entity,
- EntityOwnershipChangeState.from(wasOwner, isOwner, hasOwner), inJeopardy);
- for (ListenerActorRefEntry entry: listenerEntries) {
- ActorRef listenerActor = entry.actorFor();
-
- LOG.debug("{}: Notifying EntityOwnershipListenerActor {} with {}", logId, listenerActor, changed);
-
- listenerActor.tell(changed, ActorRef.noSender());
- }
- }
-
- private class ListenerActorRefEntry {
- final DOMEntityOwnershipListener listener;
-
- @GuardedBy("listenerLock")
- ActorRef actorRef;
-
- @GuardedBy("listenerLock")
- int referenceCount = 1;
-
- ListenerActorRefEntry(final DOMEntityOwnershipListener listener) {
- this.listener = listener;
- }
-
- ActorRef actorFor() {
- if (actorRef == null) {
- actorRef = actorContext.actorOf(EntityOwnershipListenerActor.props(listener));
-
- LOG.debug("{}: Created EntityOwnershipListenerActor {} for listener {}", logId, actorRef, listener);
- }
-
- return actorRef;
- }
-
- @Override
- public String toString() {
- return "ListenerActorRefEntry [actorRef=" + actorRef + ", referenceCount=" + referenceCount + "]";
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static java.util.Objects.requireNonNull;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.CANDIDATE_NAME_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.CANDIDATE_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_ID_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_ID_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNER_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNER_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_TYPES_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_TYPE_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_TYPE_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.candidateNodeKey;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.candidatePath;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityOwnersWithCandidate;
-
-import akka.actor.ActorRef;
-import akka.actor.ActorSelection;
-import akka.actor.Cancellable;
-import akka.cluster.Cluster;
-import akka.cluster.ClusterEvent.CurrentClusterState;
-import akka.cluster.Member;
-import akka.cluster.MemberStatus;
-import akka.pattern.Patterns;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.datastore.DatastoreContext;
-import org.opendaylight.controller.cluster.datastore.Shard;
-import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier;
-import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications;
-import org.opendaylight.controller.cluster.datastore.messages.PeerDown;
-import org.opendaylight.controller.cluster.datastore.messages.PeerUp;
-import org.opendaylight.controller.cluster.datastore.messages.SuccessReply;
-import org.opendaylight.controller.cluster.datastore.modification.DeleteModification;
-import org.opendaylight.controller.cluster.datastore.modification.MergeModification;
-import org.opendaylight.controller.cluster.datastore.modification.Modification;
-import org.opendaylight.controller.cluster.datastore.modification.WriteModification;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateAdded;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateRemoved;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.RemoveAllCandidates;
-import org.opendaylight.controller.cluster.entityownership.messages.SelectOwner;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategy;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig;
-import org.opendaylight.controller.cluster.raft.RaftState;
-import org.opendaylight.controller.cluster.raft.VotingState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import scala.concurrent.Future;
-import scala.concurrent.duration.FiniteDuration;
-
-/**
- * Special Shard for EntityOwnership.
- *
- * @author Thomas Pantelis
- */
-class EntityOwnershipShard extends Shard {
- private final MemberName localMemberName;
- private final EntityOwnershipShardCommitCoordinator commitCoordinator;
- private final EntityOwnershipListenerSupport listenerSupport;
- private final Set<MemberName> downPeerMemberNames = new HashSet<>();
- private final EntityOwnerSelectionStrategyConfig strategyConfig;
- private final Map<YangInstanceIdentifier, Cancellable> entityToScheduledOwnershipTask = new HashMap<>();
- private final EntityOwnershipStatistics entityOwnershipStatistics;
- private boolean removeAllInitialCandidates = true;
-
- protected EntityOwnershipShard(final Builder builder) {
- super(builder);
- this.localMemberName = builder.localMemberName;
- this.commitCoordinator = new EntityOwnershipShardCommitCoordinator(builder.localMemberName, LOG);
- this.listenerSupport = new EntityOwnershipListenerSupport(getContext(), persistenceId());
- this.strategyConfig = builder.ownerSelectionStrategyConfig;
- this.entityOwnershipStatistics = new EntityOwnershipStatistics();
- this.entityOwnershipStatistics.init(getDataStore());
- }
-
- private static DatastoreContext noPersistenceDatastoreContext(final DatastoreContext datastoreContext) {
- return DatastoreContext.newBuilderFrom(datastoreContext).persistent(false).build();
- }
-
- @Override
- protected void onDatastoreContext(final DatastoreContext context) {
- super.onDatastoreContext(noPersistenceDatastoreContext(context));
- }
-
- @Override
- protected void onRecoveryComplete() {
- super.onRecoveryComplete();
-
- new CandidateListChangeListener(getSelf(), persistenceId()).init(getDataStore());
- new EntityOwnerChangeListener(localMemberName, listenerSupport).init(getDataStore());
- }
-
- @Override
- public void handleNonRaftCommand(final Object message) {
- if (message instanceof RegisterCandidateLocal) {
- onRegisterCandidateLocal((RegisterCandidateLocal) message);
- } else if (message instanceof UnregisterCandidateLocal) {
- onUnregisterCandidateLocal((UnregisterCandidateLocal) message);
- } else if (message instanceof CandidateAdded) {
- onCandidateAdded((CandidateAdded) message);
- } else if (message instanceof CandidateRemoved) {
- onCandidateRemoved((CandidateRemoved) message);
- } else if (message instanceof PeerDown) {
- onPeerDown((PeerDown) message);
- } else if (message instanceof PeerUp) {
- onPeerUp((PeerUp) message);
- } else if (message instanceof RegisterListenerLocal) {
- onRegisterListenerLocal((RegisterListenerLocal) message);
- } else if (message instanceof UnregisterListenerLocal) {
- onUnregisterListenerLocal((UnregisterListenerLocal) message);
- } else if (message instanceof SelectOwner) {
- onSelectOwner((SelectOwner) message);
- } else if (message instanceof RemoveAllCandidates) {
- onRemoveAllCandidates((RemoveAllCandidates) message);
- } else if (!commitCoordinator.handleMessage(message, this)) {
- super.handleNonRaftCommand(message);
- }
- }
-
- private void onRemoveAllCandidates(final RemoveAllCandidates message) {
- LOG.debug("{}: onRemoveAllCandidates: {}", persistenceId(), message);
-
- removeCandidateFromEntities(message.getMemberName());
- }
-
- private void onSelectOwner(final SelectOwner selectOwner) {
- LOG.debug("{}: onSelectOwner: {}", persistenceId(), selectOwner);
-
- String currentOwner = getCurrentOwner(selectOwner.getEntityPath());
- if (Strings.isNullOrEmpty(currentOwner)) {
- writeNewOwner(selectOwner.getEntityPath(), newOwner(currentOwner, selectOwner.getAllCandidates(),
- selectOwner.getOwnerSelectionStrategy()));
-
- Cancellable cancellable = entityToScheduledOwnershipTask.get(selectOwner.getEntityPath());
- if (cancellable != null) {
- if (!cancellable.isCancelled()) {
- cancellable.cancel();
- }
- entityToScheduledOwnershipTask.remove(selectOwner.getEntityPath());
- }
- }
- }
-
- private void onRegisterCandidateLocal(final RegisterCandidateLocal registerCandidate) {
- LOG.debug("{}: onRegisterCandidateLocal: {}", persistenceId(), registerCandidate);
-
- NormalizedNode entityOwners = entityOwnersWithCandidate(registerCandidate.getEntity().getType(),
- registerCandidate.getEntity().getIdentifier(), localMemberName.getName());
- commitCoordinator.commitModification(new MergeModification(ENTITY_OWNERS_PATH, entityOwners), this);
-
- getSender().tell(SuccessReply.INSTANCE, getSelf());
- }
-
- private void onUnregisterCandidateLocal(final UnregisterCandidateLocal unregisterCandidate) {
- LOG.debug("{}: onUnregisterCandidateLocal: {}", persistenceId(), unregisterCandidate);
-
- DOMEntity entity = unregisterCandidate.getEntity();
- YangInstanceIdentifier candidatePath = candidatePath(entity.getType(), entity.getIdentifier(),
- localMemberName.getName());
- commitCoordinator.commitModification(new DeleteModification(candidatePath), this);
-
- getSender().tell(SuccessReply.INSTANCE, getSelf());
- }
-
- private void onRegisterListenerLocal(final RegisterListenerLocal registerListener) {
- LOG.debug("{}: onRegisterListenerLocal: {}", persistenceId(), registerListener);
-
- listenerSupport.addEntityOwnershipListener(registerListener.getEntityType(), registerListener.getListener());
-
- getSender().tell(SuccessReply.INSTANCE, getSelf());
-
- searchForEntities((entityTypeNode, entityNode) -> {
- String entityType = entityTypeNode.findChildByArg(ENTITY_TYPE_NODE_ID)
- .map(child -> child.body().toString())
- .orElse(null);
- if (registerListener.getEntityType().equals(entityType)) {
- final boolean hasOwner;
- final boolean isOwner;
-
- Optional<DataContainerChild> possibleOwner = entityNode.findChildByArg(ENTITY_OWNER_NODE_ID);
- if (possibleOwner.isPresent()) {
- isOwner = localMemberName.getName().equals(possibleOwner.get().body().toString());
- hasOwner = true;
- } else {
- isOwner = false;
- hasOwner = false;
- }
-
- DOMEntity entity = new DOMEntity(entityType,
- (YangInstanceIdentifier) entityNode.findChildByArg(ENTITY_ID_NODE_ID).get().body());
-
- listenerSupport.notifyEntityOwnershipListener(entity, false, isOwner, hasOwner,
- registerListener.getListener());
- }
- });
- }
-
- private void onUnregisterListenerLocal(final UnregisterListenerLocal unregisterListener) {
- LOG.debug("{}: onUnregisterListenerLocal: {}", persistenceId(), unregisterListener);
-
- listenerSupport.removeEntityOwnershipListener(unregisterListener.getEntityType(),
- unregisterListener.getListener());
-
- getSender().tell(SuccessReply.INSTANCE, getSelf());
- }
-
- void tryCommitModifications(final BatchedModifications modifications) {
- if (isLeader()) {
- LOG.debug("{}: Committing BatchedModifications {} locally", persistenceId(),
- modifications.getTransactionId());
-
- // Note that it's possible the commit won't get consensus and will timeout and not be applied
- // to the state. However we don't need to retry it in that case b/c it will be committed to
- // the journal first and, once a majority of followers come back on line and it is replicated,
- // it will be applied at that point.
- handleBatchedModificationsLocal(modifications, self());
- } else {
- final ActorSelection leader = getLeader();
- if (leader != null) {
- possiblyRemoveAllInitialCandidates(leader);
-
- LOG.debug("{}: Sending BatchedModifications {} to leader {}", persistenceId(),
- modifications.getTransactionId(), leader);
-
- Future<Object> future = Patterns.ask(leader, modifications, TimeUnit.SECONDS.toMillis(
- getDatastoreContext().getShardTransactionCommitTimeoutInSeconds()));
-
- Patterns.pipe(future, getContext().dispatcher()).pipeTo(getSelf(), ActorRef.noSender());
- }
- }
- }
-
- void possiblyRemoveAllInitialCandidates(final ActorSelection leader) {
- // The following handles removing all candidates on startup when re-joining with a remote leader. When a
- // follower is detected as down, the leader will re-assign new owners to entities that were owned by the
- // down member but doesn't remove the down member as a candidate, as the down node may actually be isolated
- // and still running. Therefore on startup we send an initial message to the remote leader to remove any
- // potential stale candidates we had previously registered, as it's possible a candidate may not be
- // registered by a client in the new incarnation. We have to send the RemoveAllCandidates message prior to any
- // pending registrations.
- if (removeAllInitialCandidates && leader != null) {
- removeAllInitialCandidates = false;
- if (!isLeader()) {
- LOG.debug("{} - got new leader {} on startup - sending RemoveAllCandidates", persistenceId(), leader);
-
- leader.tell(new RemoveAllCandidates(localMemberName), ActorRef.noSender());
- }
- }
- }
-
- boolean hasLeader() {
- return getLeader() != null && (!isLeader() || isLeaderActive());
- }
-
- /**
- * Determine if we are in jeopardy based on observed RAFT state.
- */
- private static boolean inJeopardy(final RaftState state) {
- switch (state) {
- case Candidate:
- case Follower:
- case Leader:
- case PreLeader:
- return false;
- case IsolatedLeader:
- return true;
- default:
- throw new IllegalStateException("Unsupported RAFT state " + state);
- }
- }
-
- private void notifyAllListeners() {
- searchForEntities((entityTypeNode, entityNode) -> {
- Optional<DataContainerChild> possibleType = entityTypeNode.findChildByArg(ENTITY_TYPE_NODE_ID);
- if (possibleType.isPresent()) {
- final boolean hasOwner;
- final boolean isOwner;
-
- Optional<DataContainerChild> possibleOwner = entityNode.findChildByArg(ENTITY_OWNER_NODE_ID);
- if (possibleOwner.isPresent()) {
- isOwner = localMemberName.getName().equals(possibleOwner.get().body().toString());
- hasOwner = true;
- } else {
- isOwner = false;
- hasOwner = false;
- }
-
- DOMEntity entity = new DOMEntity(possibleType.get().body().toString(),
- (YangInstanceIdentifier) entityNode.findChildByArg(ENTITY_ID_NODE_ID).get().body());
-
- listenerSupport.notifyEntityOwnershipListeners(entity, isOwner, isOwner, hasOwner);
- }
- });
- }
-
- @Override
- protected void onStateChanged() {
- boolean isLeader = isLeader();
- LOG.debug("{}: onStateChanged: isLeader: {}, hasLeader: {}", persistenceId(), isLeader, hasLeader());
-
- // Examine current RAFT state to see if we are in jeopardy, potentially notifying all listeners
- final boolean inJeopardy = inJeopardy(getRaftState());
- final boolean wasInJeopardy = listenerSupport.setInJeopardy(inJeopardy);
- if (inJeopardy != wasInJeopardy) {
- LOG.debug("{}: {} jeopardy state, notifying all listeners", persistenceId(),
- inJeopardy ? "entered" : "left");
- notifyAllListeners();
- }
-
- commitCoordinator.onStateChanged(this, isLeader);
-
- super.onStateChanged();
- }
-
- @Override
- protected void onLeaderChanged(final String oldLeader, final String newLeader) {
- boolean isLeader = isLeader();
- LOG.debug("{}: onLeaderChanged: oldLeader: {}, newLeader: {}, isLeader: {}", persistenceId(), oldLeader,
- newLeader, isLeader);
-
- if (isLeader) {
-
- // Re-initialize the downPeerMemberNames from the current akka Cluster state. The previous leader, if any,
- // is most likely down however it's possible we haven't received the PeerDown message yet.
- initializeDownPeerMemberNamesFromClusterState();
-
- // 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();
-
- // Re-assign owners for all members that are known to be down. In a cluster which has greater than
- // 3 nodes it is possible for some node beside the leader being down when the leadership transitions
- // it makes sense to use this event to re-assign owners for those downed nodes.
- Set<String> ownedBy = new HashSet<>(downPeerMemberNames.size() + 1);
- for (MemberName downPeerName : downPeerMemberNames) {
- ownedBy.add(downPeerName.getName());
- }
-
- // Also try to assign owners for entities that have no current owner. See explanation in onPeerUp.
- ownedBy.add("");
- selectNewOwnerForEntitiesOwnedBy(ownedBy);
- } else {
- // The leader changed - notify the coordinator to check if pending modifications need to be sent.
- // While onStateChanged also does this, this method handles the case where the shard hears from a
- // leader and stays in the follower state. In that case no behavior state change occurs.
- commitCoordinator.onStateChanged(this, isLeader);
- }
-
- super.onLeaderChanged(oldLeader, newLeader);
- }
-
- @Override
- protected void onVotingStateChangeComplete() {
- // Re-evaluate ownership for all entities - if a member changed from voting to non-voting it should lose
- // ownership and vice versa it now is a candidate to become owner.
- final List<Modification> modifications = new ArrayList<>();
- searchForEntities((entityTypeNode, entityNode) -> {
- YangInstanceIdentifier entityPath = YangInstanceIdentifier.builder(ENTITY_TYPES_PATH)
- .node(entityTypeNode.getIdentifier()).node(ENTITY_NODE_ID).node(entityNode.getIdentifier())
- .node(ENTITY_OWNER_NODE_ID).build();
-
- Optional<String> possibleOwner =
- entityNode.findChildByArg(ENTITY_OWNER_NODE_ID).map(node -> node.body().toString());
- String newOwner = newOwner(possibleOwner.orElse(null), getCandidateNames(entityNode),
- getEntityOwnerElectionStrategy(entityPath));
-
- if (!newOwner.equals(possibleOwner.orElse(""))) {
- modifications.add(new WriteModification(entityPath,
- ImmutableNodes.leafNode(ENTITY_OWNER_NODE_ID, newOwner)));
- }
- });
-
- commitCoordinator.commitModifications(modifications, this);
- }
-
- private void initializeDownPeerMemberNamesFromClusterState() {
- Optional<Cluster> cluster = getRaftActorContext().getCluster();
- if (!cluster.isPresent()) {
- return;
- }
-
- CurrentClusterState state = cluster.get().state();
- Set<Member> unreachable = state.getUnreachable();
-
- LOG.debug(
- "{}: initializeDownPeerMemberNamesFromClusterState - current downPeerMemberNames: {}, unreachable: {}",
- persistenceId(), downPeerMemberNames, unreachable);
-
- downPeerMemberNames.clear();
- for (Member m: unreachable) {
- downPeerMemberNames.add(MemberName.forName(m.getRoles().iterator().next()));
- }
-
- for (Member m: state.getMembers()) {
- if (m.status() != MemberStatus.up() && m.status() != MemberStatus.weaklyUp()) {
- LOG.debug("{}: Adding down member with status {}", persistenceId(), m.status());
- downPeerMemberNames.add(MemberName.forName(m.getRoles().iterator().next()));
- }
- }
-
- LOG.debug("{}: new downPeerMemberNames: {}", persistenceId(), downPeerMemberNames);
- }
-
- private void onCandidateRemoved(final CandidateRemoved message) {
- LOG.debug("{}: onCandidateRemoved: {}", persistenceId(), message);
-
- if (isLeader()) {
- String currentOwner = getCurrentOwner(message.getEntityPath());
- writeNewOwner(message.getEntityPath(),
- newOwner(currentOwner, message.getRemainingCandidates(),
- getEntityOwnerElectionStrategy(message.getEntityPath())));
- }
- }
-
- private EntityOwnerSelectionStrategy getEntityOwnerElectionStrategy(final YangInstanceIdentifier entityPath) {
- final String entityType = EntityOwnersModel.entityTypeFromEntityPath(entityPath);
- return strategyConfig.createStrategy(entityType, entityOwnershipStatistics.byEntityType(entityType));
- }
-
- private void onCandidateAdded(final CandidateAdded message) {
- if (!isLeader()) {
- return;
- }
-
- LOG.debug("{}: onCandidateAdded: {}", persistenceId(), message);
-
- // Since a node's candidate member is only added by the node itself, we can assume the node is up so
- // remove it from the downPeerMemberNames.
- downPeerMemberNames.remove(MemberName.forName(message.getNewCandidate()));
-
- final String currentOwner = getCurrentOwner(message.getEntityPath());
- final EntityOwnerSelectionStrategy strategy = getEntityOwnerElectionStrategy(message.getEntityPath());
-
- // Available members is all the known peers - the number of peers that are down + self
- // So if there are 2 peers and 1 is down then availableMembers will be 2
- final int availableMembers = getRaftActorContext().getPeerIds().size() - downPeerMemberNames.size() + 1;
-
- LOG.debug("{}: Using strategy {} to select owner, currentOwner = {}", persistenceId(), strategy, currentOwner);
-
- if (strategy.getSelectionDelayInMillis() == 0L) {
- writeNewOwner(message.getEntityPath(), newOwner(currentOwner, message.getAllCandidates(),
- 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(currentOwner, message.getAllCandidates(),
- strategy));
- } else {
- scheduleOwnerSelection(message.getEntityPath(), message.getAllCandidates(), strategy);
- }
- }
-
- private void onPeerDown(final PeerDown peerDown) {
- LOG.info("{}: onPeerDown: {}", persistenceId(), peerDown);
-
- MemberName downMemberName = peerDown.getMemberName();
- if (downPeerMemberNames.add(downMemberName) && isLeader()) {
- // Select new owners for entities owned by the down peer and which have other candidates. For an entity for
- // which the down peer is the only candidate, we leave it as the owner and don't clear it. This is done to
- // handle the case where the peer member process is actually still running but the node is partitioned.
- // When the partition is healed, the peer just remains as the owner. If the peer process actually restarted,
- // it will first remove all its candidates on startup. If another candidate is registered during the time
- // the peer is down, the new candidate will be selected as the new owner.
-
- selectNewOwnerForEntitiesOwnedBy(ImmutableSet.of(downMemberName.getName()));
- }
- }
-
- private void selectNewOwnerForEntitiesOwnedBy(final Set<String> ownedBy) {
- final List<Modification> modifications = new ArrayList<>();
- searchForEntitiesOwnedBy(ownedBy, (entityTypeNode, entityNode) -> {
- YangInstanceIdentifier entityPath = YangInstanceIdentifier.builder(ENTITY_TYPES_PATH)
- .node(entityTypeNode.getIdentifier()).node(ENTITY_NODE_ID).node(entityNode.getIdentifier())
- .node(ENTITY_OWNER_NODE_ID).build();
- String newOwner = newOwner(getCurrentOwner(entityPath), getCandidateNames(entityNode),
- getEntityOwnerElectionStrategy(entityPath));
-
- if (!newOwner.isEmpty()) {
- LOG.debug("{}: Found entity {}, writing new owner {}", persistenceId(), entityPath, newOwner);
-
- modifications.add(new WriteModification(entityPath,
- ImmutableNodes.leafNode(ENTITY_OWNER_NODE_ID, newOwner)));
-
- } else {
- LOG.debug("{}: Found entity {} but no other candidates - not clearing owner", persistenceId(),
- entityPath);
- }
- });
-
- commitCoordinator.commitModifications(modifications, this);
- }
-
- private void onPeerUp(final PeerUp peerUp) {
- LOG.debug("{}: onPeerUp: {}", persistenceId(), peerUp);
-
- downPeerMemberNames.remove(peerUp.getMemberName());
-
- // Notify the coordinator to check if pending modifications need to be sent. We do this here
- // to handle the case where the leader's peer address isn't known yet when a prior state or
- // leader change occurred.
- commitCoordinator.onStateChanged(this, isLeader());
-
- if (isLeader()) {
- // Try to assign owners for entities that have no current owner. It's possible the peer that is now up
- // had previously registered as a candidate and was the only candidate but the owner write tx couldn't be
- // committed due to a leader change. Eg, the leader is able to successfully commit the candidate add tx but
- // becomes isolated before it can commit the owner change and switches to follower. The majority partition
- // with a new leader has the candidate but the entity has no owner. When the partition is healed and the
- // previously isolated leader reconnects, we'll receive onPeerUp and, if there's still no owner, the
- // previous leader will gain ownership.
- selectNewOwnerForEntitiesOwnedBy(ImmutableSet.of(""));
- }
- }
-
- private static Collection<String> getCandidateNames(final MapEntryNode entity) {
- return entity.findChildByArg(CANDIDATE_NODE_ID).map(child -> {
- Collection<MapEntryNode> candidates = ((MapNode) child).body();
- Collection<String> candidateNames = new ArrayList<>(candidates.size());
- for (MapEntryNode candidate: candidates) {
- candidateNames.add(candidate.findChildByArg(CANDIDATE_NAME_NODE_ID).get().body().toString());
- }
- return candidateNames;
- }).orElse(ImmutableList.of());
- }
-
- private void searchForEntitiesOwnedBy(final Set<String> ownedBy, final EntityWalker walker) {
- LOG.debug("{}: Searching for entities owned by {}", persistenceId(), ownedBy);
-
- searchForEntities((entityTypeNode, entityNode) -> {
- String currentOwner = entityNode.findChildByArg(ENTITY_OWNER_NODE_ID)
- .map(child -> child.body().toString())
- .orElse("");
- if (ownedBy.contains(currentOwner)) {
- walker.onEntity(entityTypeNode, entityNode);
- }
- });
- }
-
- private void removeCandidateFromEntities(final MemberName member) {
- final List<Modification> modifications = new ArrayList<>();
- searchForEntities((entityTypeNode, entityNode) -> {
- if (hasCandidate(entityNode, member)) {
- YangInstanceIdentifier entityId = (YangInstanceIdentifier) entityNode.getIdentifier()
- .getValue(ENTITY_ID_QNAME);
- YangInstanceIdentifier candidatePath = candidatePath(entityTypeNode.getIdentifier()
- .getValue(ENTITY_TYPE_QNAME).toString(), entityId, member.getName());
-
- LOG.info("{}: Found entity {}, removing candidate {}, path {}", persistenceId(), entityId,
- member, candidatePath);
-
- modifications.add(new DeleteModification(candidatePath));
- }
- });
-
- commitCoordinator.commitModifications(modifications, this);
- }
-
- private static boolean hasCandidate(final MapEntryNode entity, final MemberName candidateName) {
- return entity.findChildByArg(CANDIDATE_NODE_ID)
- .flatMap(child -> ((MapNode)child).findChildByArg(candidateNodeKey(candidateName.getName())))
- .isPresent();
- }
-
- private void searchForEntities(final EntityWalker walker) {
- Optional<NormalizedNode> possibleEntityTypes = getDataStore().readNode(ENTITY_TYPES_PATH);
- if (!possibleEntityTypes.isPresent()) {
- return;
- }
-
- for (MapEntryNode entityType : ((MapNode) possibleEntityTypes.get()).body()) {
- Optional<DataContainerChild> possibleEntities = entityType.findChildByArg(ENTITY_NODE_ID);
- if (!possibleEntities.isPresent()) {
- // shouldn't happen but handle anyway
- continue;
- }
-
- for (MapEntryNode entity: ((MapNode) possibleEntities.get()).body()) {
- walker.onEntity(entityType, entity);
- }
- }
- }
-
- private void writeNewOwner(final YangInstanceIdentifier entityPath, final String newOwner) {
- LOG.debug("{}: Writing new owner {} for entity {}", persistenceId(), newOwner, entityPath);
-
- commitCoordinator.commitModification(new WriteModification(entityPath.node(ENTITY_OWNER_QNAME),
- ImmutableNodes.leafNode(ENTITY_OWNER_NODE_ID, newOwner)), this);
- }
-
- /**
- * Schedule a new owner selection job. Cancelling any outstanding job if it has not been cancelled.
- */
- private void scheduleOwnerSelection(final YangInstanceIdentifier entityPath, final Collection<String> allCandidates,
- final EntityOwnerSelectionStrategy strategy) {
- cancelOwnerSelectionTask(entityPath);
-
- LOG.debug("{}: Scheduling owner selection after {} ms", persistenceId(), strategy.getSelectionDelayInMillis());
-
- final Cancellable lastScheduledTask = context().system().scheduler().scheduleOnce(
- FiniteDuration.apply(strategy.getSelectionDelayInMillis(), TimeUnit.MILLISECONDS), self(),
- new SelectOwner(entityPath, allCandidates, strategy), context().system().dispatcher(), self());
-
- entityToScheduledOwnershipTask.put(entityPath, lastScheduledTask);
- }
-
- private void cancelOwnerSelectionTask(final YangInstanceIdentifier entityPath) {
- final Cancellable lastScheduledTask = entityToScheduledOwnershipTask.get(entityPath);
- if (lastScheduledTask != null && !lastScheduledTask.isCancelled()) {
- lastScheduledTask.cancel();
- }
- }
-
- private String newOwner(final String currentOwner, final Collection<String> candidates,
- final EntityOwnerSelectionStrategy ownerSelectionStrategy) {
- Collection<String> viableCandidates = getViableCandidates(candidates);
- if (viableCandidates.isEmpty()) {
- return "";
- }
- return ownerSelectionStrategy.newOwner(currentOwner, viableCandidates);
- }
-
- private Collection<String> getViableCandidates(final Collection<String> candidates) {
- Map<MemberName, VotingState> memberToVotingState = new HashMap<>();
- getRaftActorContext().getPeers().forEach(peerInfo -> memberToVotingState.put(
- ShardIdentifier.fromShardIdString(peerInfo.getId()).getMemberName(), peerInfo.getVotingState()));
-
- Collection<String> viableCandidates = new ArrayList<>();
-
- for (String candidate : candidates) {
- MemberName memberName = MemberName.forName(candidate);
- if (memberToVotingState.get(memberName) != VotingState.NON_VOTING
- && !downPeerMemberNames.contains(memberName)) {
- viableCandidates.add(candidate);
- }
- }
- return viableCandidates;
- }
-
- private String getCurrentOwner(final YangInstanceIdentifier entityId) {
- return getDataStore().readNode(entityId.node(ENTITY_OWNER_QNAME))
- .map(owner -> owner.body().toString())
- .orElse(null);
- }
-
- @FunctionalInterface
- private interface EntityWalker {
- void onEntity(MapEntryNode entityTypeNode, MapEntryNode entityNode);
- }
-
- public static Builder newBuilder() {
- return new Builder();
- }
-
- static class Builder extends Shard.AbstractBuilder<Builder, EntityOwnershipShard> {
- private MemberName localMemberName;
- private EntityOwnerSelectionStrategyConfig ownerSelectionStrategyConfig;
-
- protected Builder() {
- super(EntityOwnershipShard.class);
- }
-
- Builder localMemberName(final MemberName newLocalMemberName) {
- checkSealed();
- this.localMemberName = newLocalMemberName;
- return this;
- }
-
- Builder ownerSelectionStrategyConfig(final EntityOwnerSelectionStrategyConfig newOwnerSelectionStrategyConfig) {
- checkSealed();
- this.ownerSelectionStrategyConfig = newOwnerSelectionStrategyConfig;
- return this;
- }
-
- @Override
- protected void verify() {
- super.verify();
- requireNonNull(localMemberName, "localMemberName should not be null");
- requireNonNull(ownerSelectionStrategyConfig, "ownerSelectionStrategyConfig should not be null");
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static java.util.Objects.requireNonNull;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNER_QNAME;
-
-import akka.actor.ActorRef;
-import akka.actor.Cancellable;
-import akka.actor.Status.Failure;
-import com.google.common.collect.ImmutableList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
-import org.opendaylight.controller.cluster.access.concepts.FrontendIdentifier;
-import org.opendaylight.controller.cluster.access.concepts.FrontendType;
-import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
-import org.opendaylight.controller.cluster.datastore.DataStoreVersions;
-import org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException;
-import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications;
-import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply;
-import org.opendaylight.controller.cluster.datastore.modification.Modification;
-import org.opendaylight.controller.cluster.datastore.modification.WriteModification;
-import org.slf4j.Logger;
-import scala.concurrent.duration.FiniteDuration;
-
-/**
- * Handles commits and retries for the EntityOwnershipShard.
- *
- * @author Thomas Pantelis
- */
-class EntityOwnershipShardCommitCoordinator {
- private static final Object COMMIT_RETRY_MESSAGE = new Object() {
- @Override
- public String toString() {
- return "entityCommitRetry";
- }
- };
- private static final FrontendType FRONTEND_TYPE = FrontendType.forName("entity-ownership-internal");
-
- private final Queue<Modification> pendingModifications = new LinkedList<>();
- private final LocalHistoryIdentifier historyId;
- private final Logger log;
-
- private BatchedModifications inflightCommit;
- private Cancellable retryCommitSchedule;
- private long transactionIDCounter = 0;
-
- EntityOwnershipShardCommitCoordinator(final MemberName localMemberName, final Logger log) {
- this.log = requireNonNull(log);
- historyId = new LocalHistoryIdentifier(
- ClientIdentifier.create(FrontendIdentifier.create(localMemberName, FRONTEND_TYPE), 0), 0);
- }
-
- boolean handleMessage(final Object message, final EntityOwnershipShard shard) {
- boolean handled = true;
- if (CommitTransactionReply.isSerializedType(message)) {
- // Successful reply from a local commit.
- inflightCommitSucceeded(shard);
- } else if (message instanceof akka.actor.Status.Failure) {
- // Failure reply from a local commit.
- inflightCommitFailure(((Failure) message).cause(), shard);
- } else if (COMMIT_RETRY_MESSAGE.equals(message)) {
- retryInflightCommit(shard);
- } else {
- handled = false;
- }
-
- return handled;
- }
-
- private void retryInflightCommit(final EntityOwnershipShard shard) {
- // Shouldn't be null happen but verify anyway
- if (inflightCommit == null) {
- return;
- }
-
- if (shard.hasLeader()) {
- log.debug("Retrying commit for BatchedModifications {}", inflightCommit.getTransactionId());
-
- shard.tryCommitModifications(inflightCommit);
- } else {
- scheduleInflightCommitRetry(shard);
- }
- }
-
- void inflightCommitFailure(final Throwable cause, final EntityOwnershipShard shard) {
- // This should've originated from a failed inflight commit but verify anyway
- if (inflightCommit == null) {
- return;
- }
-
- log.debug("Inflight BatchedModifications {} commit failed", inflightCommit.getTransactionId(), cause);
-
- if (!(cause instanceof NoShardLeaderException)) {
- // If the failure is other than NoShardLeaderException the commit may have been partially
- // processed so retry with a new transaction ID to be safe.
- newInflightCommitWithDifferentTransactionID();
- }
-
- scheduleInflightCommitRetry(shard);
- }
-
- private void scheduleInflightCommitRetry(final EntityOwnershipShard shard) {
- FiniteDuration duration = shard.getDatastoreContext().getShardRaftConfig().getElectionTimeOutInterval();
-
- log.debug("Scheduling retry for BatchedModifications commit {} in {}",
- inflightCommit.getTransactionId(), duration);
-
- retryCommitSchedule = shard.getContext().system().scheduler().scheduleOnce(duration, shard.getSelf(),
- COMMIT_RETRY_MESSAGE, shard.getContext().dispatcher(), ActorRef.noSender());
- }
-
- void inflightCommitSucceeded(final EntityOwnershipShard shard) {
- // Shouldn't be null but verify anyway
- if (inflightCommit == null) {
- return;
- }
-
- if (retryCommitSchedule != null) {
- retryCommitSchedule.cancel();
- }
-
- log.debug("BatchedModifications commit {} succeeded", inflightCommit.getTransactionId());
-
- inflightCommit = null;
- commitNextBatch(shard);
- }
-
- void commitNextBatch(final EntityOwnershipShard shard) {
- if (inflightCommit != null || pendingModifications.isEmpty() || !shard.hasLeader()) {
- return;
- }
-
- inflightCommit = newBatchedModifications();
- Iterator<Modification> iter = pendingModifications.iterator();
- while (iter.hasNext()) {
- inflightCommit.addModification(iter.next());
- iter.remove();
- if (inflightCommit.getModifications().size()
- >= shard.getDatastoreContext().getShardBatchedModificationCount()) {
- break;
- }
- }
-
- log.debug("Committing next BatchedModifications {}, size {}", inflightCommit.getTransactionId(),
- inflightCommit.getModifications().size());
-
- shard.tryCommitModifications(inflightCommit);
- }
-
- void commitModification(final Modification modification, final EntityOwnershipShard shard) {
- commitModifications(ImmutableList.of(modification), shard);
- }
-
- void commitModifications(final List<Modification> modifications, final EntityOwnershipShard shard) {
- if (modifications.isEmpty()) {
- return;
- }
-
- boolean hasLeader = shard.hasLeader();
- if (inflightCommit != null || !hasLeader) {
- if (log.isDebugEnabled()) {
- log.debug("{} - adding modifications to pending",
- inflightCommit != null ? "A commit is inflight" : "No shard leader");
- }
-
- pendingModifications.addAll(modifications);
- } else {
- inflightCommit = newBatchedModifications();
- inflightCommit.addModifications(modifications);
- shard.tryCommitModifications(inflightCommit);
- }
- }
-
- void onStateChanged(final EntityOwnershipShard shard, final boolean isLeader) {
- shard.possiblyRemoveAllInitialCandidates(shard.getLeader());
-
- possiblyPrunePendingCommits(shard, isLeader);
-
- if (!isLeader && inflightCommit != null) {
- // We're no longer the leader but we have an inflight local commit. This likely means we didn't get
- // consensus for the commit and switched to follower due to another node with a higher term. We
- // can't be sure if the commit was replicated to any node so we retry it here with a new
- // transaction ID.
- if (retryCommitSchedule != null) {
- retryCommitSchedule.cancel();
- }
-
- newInflightCommitWithDifferentTransactionID();
- retryInflightCommit(shard);
- } else {
- commitNextBatch(shard);
- }
- }
-
- private void possiblyPrunePendingCommits(final EntityOwnershipShard shard, final boolean isLeader) {
- // If we were the leader and transitioned to follower, we'll try to forward pending commits to the new leader.
- // However certain commits, e.g. entity owner changes, should only be committed by a valid leader as the
- // criteria used to determine the commit may be stale. Since we're no longer a valid leader, we should not
- // forward such commits thus we prune the pending modifications. We still should forward local candidate change
- // commits.
- if (shard.hasLeader() && !isLeader) {
- // We may have already submitted a transaction for replication and commit. We don't need the base Shard to
- // forward it since we also have it stored in the inflightCommit and handle retries. So we just clear
- // pending transactions and drop them.
- shard.convertPendingTransactionsToMessages();
-
- // Prune the inflightCommit.
- if (inflightCommit != null) {
- inflightCommit = pruneModifications(inflightCommit);
- }
-
- // Prune the subsequent pending modifications.
- pendingModifications.removeIf(mod -> !canForwardModificationToNewLeader(mod));
- }
- }
-
- private @Nullable BatchedModifications pruneModifications(final BatchedModifications toPrune) {
- BatchedModifications prunedModifications = new BatchedModifications(toPrune.getTransactionId(),
- toPrune.getVersion());
- prunedModifications.setDoCommitOnReady(toPrune.isDoCommitOnReady());
- if (toPrune.isReady()) {
- prunedModifications.setReady(toPrune.getParticipatingShardNames());
- }
- prunedModifications.setTotalMessagesSent(toPrune.getTotalMessagesSent());
- for (Modification mod: toPrune.getModifications()) {
- if (canForwardModificationToNewLeader(mod)) {
- prunedModifications.addModification(mod);
- }
- }
-
- return !prunedModifications.getModifications().isEmpty() ? prunedModifications : null;
- }
-
- private boolean canForwardModificationToNewLeader(final Modification mod) {
- // If this is a WRITE of entity owner we don't want to forward it to a new leader since the criteria used
- // to determine the new owner might be stale.
- if (mod instanceof WriteModification) {
- WriteModification writeMod = (WriteModification)mod;
- boolean canForward = !writeMod.getPath().getLastPathArgument().getNodeType().equals(ENTITY_OWNER_QNAME);
-
- if (!canForward) {
- log.debug("Not forwarding WRITE modification for {} to new leader", writeMod.getPath());
- }
-
- return canForward;
- }
-
- return true;
- }
-
- private void newInflightCommitWithDifferentTransactionID() {
- BatchedModifications newBatchedModifications = newBatchedModifications();
- newBatchedModifications.addModifications(inflightCommit.getModifications());
- inflightCommit = newBatchedModifications;
- }
-
- private BatchedModifications newBatchedModifications() {
- BatchedModifications modifications = new BatchedModifications(
- new TransactionIdentifier(historyId, ++transactionIDCounter), DataStoreVersions.CURRENT_VERSION);
- modifications.setDoCommitOnReady(true);
- modifications.setReady();
- modifications.setTotalMessagesSent(1);
- return modifications;
- }
-}
+++ /dev/null
-/*
- * 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.entityownership;
-
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityTypeFromEntityPath;
-
-import com.google.common.base.Strings;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
-import tech.pantheon.triemap.TrieMap;
-
-/**
- * EntityOwnershipStatistics is a utility class that keeps track of ownership statistics for the candidates and
- * caches it for quick count queries.
- * <p/>
- * While the entity ownership model does maintain the information about which entity is owned by which candidate
- * finding out how many entities of a given type are owned by a given candidate is not an efficient query.
- */
-class EntityOwnershipStatistics extends AbstractEntityOwnerChangeListener {
- private final TrieMap<String, TrieMap<String, Long>> statistics = TrieMap.create();
-
- EntityOwnershipStatistics() {
- // Hidden on purpose
- }
-
- @Override
- public void onInitialData() {
- // No-op
- }
-
- @Override
- public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
- for (DataTreeCandidate change : changes) {
- DataTreeCandidateNode changeRoot = change.getRootNode();
- LeafNode<?> ownerLeaf = (LeafNode<?>) changeRoot.getDataAfter().get();
- String entityType = entityTypeFromEntityPath(change.getRootPath());
- String newOwner = extractOwner(ownerLeaf);
- if (!Strings.isNullOrEmpty(newOwner)) {
- updateStatistics(entityType, newOwner, 1);
- }
-
- Optional<NormalizedNode> dataBefore = changeRoot.getDataBefore();
- if (dataBefore.isPresent()) {
- String origOwner = extractOwner((LeafNode<?>) changeRoot.getDataBefore().get());
- if (!Strings.isNullOrEmpty(origOwner)) {
- updateStatistics(entityType, origOwner, -1);
- }
- }
- }
- }
-
- Map<String, Map<String, Long>> all() {
- Map<String, Map<String, Long>> snapshot = new HashMap<>();
- for (String entityType : statistics.immutableSnapshot().keySet()) {
- snapshot.put(entityType, byEntityType(entityType));
- }
- return snapshot;
- }
-
- Map<String, Long> byEntityType(final String entityType) {
- if (statistics.get(entityType) != null) {
- return statistics.get(entityType).immutableSnapshot();
- }
- return new HashMap<>();
- }
-
- private void updateStatistics(final String entityType, final String candidateName, final long count) {
- TrieMap<String, Long> map = statistics.get(entityType);
- if (map == null) {
- map = TrieMap.create();
- map.put(candidateName, count);
- statistics.put(entityType, map);
- } else {
- map.merge(candidateName, count, (ownedEntities, addedEntities) -> ownedEntities + addedEntities);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.entityownership;
-
-import com.google.common.annotations.Beta;
-import java.util.Map;
-import java.util.Optional;
-import org.opendaylight.controller.cluster.datastore.DistributedDataStoreInterface;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfigReader;
-import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
-import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Beta
-@Component(immediate = true, configurationPid = "org.opendaylight.controller.cluster.entity.owner.selection.strategies",
- property = "type=default")
-public final class OSGiDistributedEntityOwnershipService implements DOMEntityOwnershipService {
- private static final Logger LOG = LoggerFactory.getLogger(OSGiDistributedEntityOwnershipService.class);
-
- @Reference(target = "(type=distributed-operational)")
- DistributedDataStoreInterface operDatastore = null;
-
- private DistributedEntityOwnershipService delegate;
-
- @Override
- public DOMEntityOwnershipCandidateRegistration registerCandidate(final DOMEntity entity)
- throws CandidateAlreadyRegisteredException {
- return delegate.registerCandidate(entity);
- }
-
- @Override
- public DOMEntityOwnershipListenerRegistration registerListener(final String entityType,
- final DOMEntityOwnershipListener listener) {
- return delegate.registerListener(entityType, listener);
- }
-
- @Override
- public Optional<EntityOwnershipState> getOwnershipState(final DOMEntity forEntity) {
- return delegate.getOwnershipState(forEntity);
- }
-
- @Override
- public boolean isCandidateRegistered(final DOMEntity forEntity) {
- return delegate.isCandidateRegistered(forEntity);
- }
-
- @Activate
- // FIXME: 3.0.0: properties are keyed by String, this should be Map<String, Object>
- void activate(final Map<Object, Object> properties) {
- LOG.info("Distributed Entity Ownership Service starting");
- delegate = DistributedEntityOwnershipService.start(operDatastore.getActorUtils(),
- EntityOwnerSelectionStrategyConfigReader.loadStrategyWithConfig(properties));
- LOG.info("Distributed Entity Ownership Service started");
- }
-
- @Deactivate
- void deactivate() {
- LOG.info("Distributed Entity Ownership Service stopping");
- delegate.close();
- LOG.info("Distributed Entity Ownership Service stopped");
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.messages;
-
-import java.util.Collection;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-
-/**
- * Message sent when a new candidate is added for an entity.
- *
- * @author Moiz Raja
- * @author Thomas Pantelis
- */
-public class CandidateAdded {
- private final YangInstanceIdentifier entityPath;
- private final Collection<String> allCandidates;
- private final String newCandidate;
-
- public CandidateAdded(final YangInstanceIdentifier entityPath, final String newCandidate,
- final Collection<String> allCandidates) {
- this.entityPath = entityPath;
- this.newCandidate = newCandidate;
- this.allCandidates = allCandidates;
- }
-
- public YangInstanceIdentifier getEntityPath() {
- return entityPath;
- }
-
- public Collection<String> getAllCandidates() {
- return allCandidates;
- }
-
- public String getNewCandidate() {
- return newCandidate;
- }
-
- @Override
- public String toString() {
- return "CandidateAdded [entityPath=" + entityPath + ", newCandidate=" + newCandidate + ", allCandidates="
- + allCandidates + "]";
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.messages;
-
-import java.util.Collection;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-
-/**
- * Message sent when a candidate is removed for an entity.
- *
- * @author Moiz Raja
- * @author Thomas Pantelis
- */
-public class CandidateRemoved {
- private final YangInstanceIdentifier entityPath;
- private final String removedCandidate;
- private final Collection<String> remainingCandidates;
-
- public CandidateRemoved(final YangInstanceIdentifier entityPath, final String removedCandidate,
- final Collection<String> remainingCandidates) {
- this.entityPath = entityPath;
- this.removedCandidate = removedCandidate;
- this.remainingCandidates = remainingCandidates;
- }
-
- public YangInstanceIdentifier getEntityPath() {
- return entityPath;
- }
-
- public String getRemovedCandidate() {
- return removedCandidate;
- }
-
- public Collection<String> getRemainingCandidates() {
- return remainingCandidates;
- }
-
- @Override
- public String toString() {
- return "CandidateRemoved [entityPath=" + entityPath + ", removedCandidate=" + removedCandidate
- + ", remainingCandidates=" + remainingCandidates + "]";
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership.messages;
-
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-
-/**
- * Message sent to the local EntityOwnershipShard to register a candidate.
- *
- * @author Thomas Pantelis
- */
-public class RegisterCandidateLocal {
- private final DOMEntity entity;
-
- public RegisterCandidateLocal(final DOMEntity entity) {
- this.entity = entity;
- }
-
- public DOMEntity getEntity() {
- return entity;
- }
-
- @Override
- public String toString() {
- return "RegisterCandidateLocal [entity=" + entity + "]";
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership.messages;
-
-import static java.util.Objects.requireNonNull;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-
-/**
- * Message sent to the local EntityOwnershipShard to register an EntityOwnershipListener.
- *
- * @author Thomas Pantelis
- */
-@NonNullByDefault
-public class RegisterListenerLocal {
- private final DOMEntityOwnershipListener listener;
- private final String entityType;
-
- public RegisterListenerLocal(final DOMEntityOwnershipListener listener, final String entityType) {
- this.listener = requireNonNull(listener, "listener cannot be null");
- this.entityType = requireNonNull(entityType, "entityType cannot be null");
- }
-
- public DOMEntityOwnershipListener getListener() {
- return listener;
- }
-
- public String getEntityType() {
- return entityType;
- }
-
- @Override
- public String toString() {
- return "RegisterListenerLocal [entityType=" + entityType + ", listener=" + listener + "]";
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2016 2015 Brocade Communications 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.entityownership.messages;
-
-import java.io.Serializable;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-
-/**
- * Message sent by an EntityOwnershipShard to its leader on startup to remove all its candidates.
- *
- * @author Thomas Pantelis
- */
-public class RemoveAllCandidates implements Serializable {
- private static final long serialVersionUID = 1L;
-
- private final MemberName memberName;
-
- public RemoveAllCandidates(final MemberName memberName) {
- this.memberName = memberName;
- }
-
- public MemberName getMemberName() {
- return memberName;
- }
-
- @Override
- public String toString() {
- return "RemoveAllCandidates [memberName=" + memberName + "]";
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.messages;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Collection;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategy;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-
-/**
- * Message sent when a new owner needs to be selected.
- */
-public class SelectOwner {
- private final YangInstanceIdentifier entityPath;
- private final Collection<String> allCandidates;
- private final EntityOwnerSelectionStrategy ownerSelectionStrategy;
-
- public SelectOwner(final YangInstanceIdentifier entityPath, final Collection<String> allCandidates,
- final EntityOwnerSelectionStrategy ownerSelectionStrategy) {
- this.entityPath = requireNonNull(entityPath, "entityPath should not be null");
- this.allCandidates = requireNonNull(allCandidates, "allCandidates should not be null");
- this.ownerSelectionStrategy = requireNonNull(ownerSelectionStrategy,
- "ownerSelectionStrategy should not be null");
- }
-
- public YangInstanceIdentifier getEntityPath() {
- return entityPath;
- }
-
- public Collection<String> getAllCandidates() {
- return allCandidates;
- }
-
- public EntityOwnerSelectionStrategy getOwnerSelectionStrategy() {
- return ownerSelectionStrategy;
- }
-
- @Override
- public String toString() {
- return "SelectOwner [entityPath=" + entityPath + ", allCandidates=" + allCandidates
- + ", ownerSelectionStrategy=" + ownerSelectionStrategy + "]";
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership.messages;
-
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-
-/**
- * Message sent to the local EntityOwnershipShard to unregister a candidate.
- *
- * @author Thomas Pantelis
- */
-public class UnregisterCandidateLocal {
- private final DOMEntity entity;
-
- public UnregisterCandidateLocal(final DOMEntity entity) {
- this.entity = entity;
- }
-
- public DOMEntity getEntity() {
- return entity;
- }
-
- @Override
- public String toString() {
- return "UnregisterCandidateLocal [entity=" + entity + "]";
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership.messages;
-
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-
-/**
- * Message sent to the local EntityOwnershipShard to unregister an EntityOwnershipListener.
- *
- * @author Thomas Pantelis
- */
-public class UnregisterListenerLocal {
- private final DOMEntityOwnershipListener listener;
- private final String entityType;
-
- public UnregisterListenerLocal(final DOMEntityOwnershipListener listener, final String entityType) {
- this.listener = listener;
- this.entityType = entityType;
- }
-
- public DOMEntityOwnershipListener getListener() {
- return listener;
- }
-
- public String getEntityType() {
- return entityType;
- }
-
- @Override
- public String toString() {
- return "UnregisterListenerLocal [entityType=" + entityType + ", listener=" + listener + "]";
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import java.util.Map;
-
-public abstract class AbstractEntityOwnerSelectionStrategy implements EntityOwnerSelectionStrategy {
-
- private final long selectionDelayInMillis;
- private final Map<String, Long> initialStatistics;
-
- protected AbstractEntityOwnerSelectionStrategy(final long selectionDelayInMillis,
- final Map<String, Long> initialStatistics) {
- this.selectionDelayInMillis = selectionDelayInMillis;
- this.initialStatistics = initialStatistics;
- }
-
- @Override
- public long getSelectionDelayInMillis() {
- return selectionDelayInMillis;
- }
-
- public Map<String, Long> getInitialStatistics() {
- return initialStatistics;
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import java.util.Collection;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * An EntityOwnerSelectionStrategy is to be used by the EntityOwnershipShard to select a new owner from a collection
- * of candidates.
- */
-public interface EntityOwnerSelectionStrategy {
- /**
- * Returns the time in millis owner selection should be delayed.
- *
- * @return the time in millis owner selection should be delayed
- */
- long getSelectionDelayInMillis();
-
- /**
- * Selects a new owner from the list of viable candidates.
- *
- * @param currentOwner the current owner of the entity if any, null otherwise
- * @param viableCandidates the available candidates from which to choose the new owner
- * @return the new owner
- */
- String newOwner(@Nullable String currentOwner, Collection<String> viableCandidates);
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
-import java.util.Map;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * FIXME: this is simple registry service, except it also loads classes.
- */
-public final class EntityOwnerSelectionStrategyConfig {
- private static final Logger LOG = LoggerFactory.getLogger(EntityOwnerSelectionStrategyConfig.class);
- private final Map<String, StrategyInfo> entityTypeToStrategyInfo = new HashMap<>();
- private final Map<String, EntityOwnerSelectionStrategy> entityTypeToOwnerSelectionStrategy = new HashMap<>();
-
- private EntityOwnerSelectionStrategyConfig() {
-
- }
-
- public boolean isStrategyConfigured(final String entityType) {
- return entityTypeToStrategyInfo.get(entityType) != null;
- }
-
- public EntityOwnerSelectionStrategy createStrategy(final String entityType,
- final Map<String, Long> initialStatistics) {
- final EntityOwnerSelectionStrategy strategy;
- final EntityOwnerSelectionStrategy existingStrategy = entityTypeToOwnerSelectionStrategy.get(entityType);
- if (existingStrategy != null) {
- strategy = existingStrategy;
- } else {
- EntityOwnerSelectionStrategyConfig.StrategyInfo strategyInfo = entityTypeToStrategyInfo.get(entityType);
- if (strategyInfo == null) {
- strategy = FirstCandidateSelectionStrategy.INSTANCE;
- } else {
- strategy = strategyInfo.createStrategy(initialStatistics);
- }
- entityTypeToOwnerSelectionStrategy.put(entityType, strategy);
- }
- return strategy;
- }
-
- /**
- * This class should not exist. It contains a single long, which is passed to the constructor (via reflection).
- * We are getting that information from a BundleContext. We are running in OSGi environment, hence this class
- * needs to be deployed in its own bundle, with its own configuration.
- * If this is used internally, it needs to be relocated into a separate package along with the implementation
- * using it.
- *
- * @deprecated FIXME: THIS IS CONFIGURATION FOR A CUSTOM-LOADED CLASS CONSTRUCTOR
- */
- @Deprecated
- public void clearStrategies() {
- entityTypeToOwnerSelectionStrategy.clear();
- }
-
- private static final class StrategyInfo {
- private final Class<? extends EntityOwnerSelectionStrategy> strategyClass;
- private final long delay;
-
- private StrategyInfo(final Class<? extends EntityOwnerSelectionStrategy> strategyClass, final long delay) {
- this.strategyClass = strategyClass;
- this.delay = delay;
- }
-
- public EntityOwnerSelectionStrategy createStrategy(final Map<String, Long> initialStatistics) {
- try {
- return strategyClass.getDeclaredConstructor(long.class, Map.class)
- .newInstance(delay, initialStatistics);
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException
- | NoSuchMethodException e) {
- LOG.warn("could not create custom strategy", e);
- }
- return FirstCandidateSelectionStrategy.INSTANCE;
- }
- }
-
- public static Builder newBuilder() {
- return new Builder(new EntityOwnerSelectionStrategyConfig());
- }
-
- public static final class Builder {
- private final EntityOwnerSelectionStrategyConfig config;
-
- Builder(final EntityOwnerSelectionStrategyConfig config) {
- this.config = config;
- }
-
- public Builder addStrategy(final String entityType,
- final Class<? extends EntityOwnerSelectionStrategy> strategy, final long delay) {
- config.entityTypeToStrategyInfo.put(entityType, new StrategyInfo(strategy, delay));
- return this;
- }
-
- public EntityOwnerSelectionStrategyConfig build() {
- return this.config;
- }
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import com.google.common.base.Preconditions;
-import java.util.Map;
-import java.util.Map.Entry;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig.Builder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Reads the entity owner selection strategy config.
- *
- */
-public final class EntityOwnerSelectionStrategyConfigReader {
-
- private static final Logger LOG = LoggerFactory.getLogger(EntityOwnerSelectionStrategyConfigReader.class);
- private static final String ENTITY_TYPE_PREFIX = "entity.type.";
-
- private EntityOwnerSelectionStrategyConfigReader() {
- // Hidden on purpose
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- public static EntityOwnerSelectionStrategyConfig loadStrategyWithConfig(final Map<Object, Object> props) {
- final EntityOwnerSelectionStrategyConfig.Builder builder = EntityOwnerSelectionStrategyConfig.newBuilder();
-
- if (props != null && !props.isEmpty()) {
- parseConfiguration(builder, props);
- } else {
- if (props == null) {
- LOG.debug("Could not read strategy configuration file, will use default configuration.");
- } else {
- LOG.debug("Configuration file is empty, will use default configuration.");
- }
- }
- return builder.build();
- }
-
- private static EntityOwnerSelectionStrategyConfig parseConfiguration(final Builder builder,
- final Map<Object, Object> properties) {
-
- for (final Entry<Object, Object> entry : properties.entrySet()) {
- final String key = (String) entry.getKey();
- if (!key.startsWith(ENTITY_TYPE_PREFIX)) {
- LOG.debug("Ignoring non-conforming property key : {}", key);
- continue;
- }
-
- final String[] strategyClassAndDelay = ((String) properties.get(key)).split(",");
- final Class<? extends EntityOwnerSelectionStrategy> aClass = loadClass(strategyClassAndDelay[0]);
-
- final long delay;
- if (strategyClassAndDelay.length > 1) {
- delay = Long.parseLong(strategyClassAndDelay[1]);
- } else {
- delay = 0;
- }
-
- final String entityType = key.substring(key.lastIndexOf(".") + 1);
- builder.addStrategy(entityType, aClass, delay);
- LOG.debug("Entity Type '{}' using strategy {} delay {}", entityType, aClass, delay);
- }
-
- return builder.build();
- }
-
- @SuppressWarnings("unchecked")
- private static Class<? extends EntityOwnerSelectionStrategy> loadClass(final String strategyClassAndDelay) {
- final Class<?> clazz;
- try {
- clazz = EntityOwnerSelectionStrategyConfigReader.class.getClassLoader().loadClass(strategyClassAndDelay);
- } catch (final ClassNotFoundException e) {
- throw new IllegalArgumentException("Failed to load strategy " + strategyClassAndDelay, e);
- }
-
- Preconditions.checkArgument(EntityOwnerSelectionStrategy.class.isAssignableFrom(clazz),
- "Selected implementation %s must implement EntityOwnerSelectionStrategy, clazz");
-
- return (Class<? extends EntityOwnerSelectionStrategy>) clazz;
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import com.google.common.base.Preconditions;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * The FirstCandidateSelectionStrategy always selects the first viable candidate from the list of candidates.
- */
-public class FirstCandidateSelectionStrategy extends AbstractEntityOwnerSelectionStrategy {
-
- public static final FirstCandidateSelectionStrategy INSTANCE =
- new FirstCandidateSelectionStrategy(0L, Collections.emptyMap());
-
- public FirstCandidateSelectionStrategy(final long selectionDelayInMillis,
- final Map<String, Long> initialStatistics) {
- super(selectionDelayInMillis, initialStatistics);
- }
-
- @Override
- public String newOwner(final String currentOwner, final Collection<String> viableCandidates) {
- Preconditions.checkArgument(viableCandidates.size() > 0, "No viable candidates provided");
- return viableCandidates.iterator().next();
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Objects.requireNonNullElse;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import java.util.Collection;
-import java.util.HashMap;
-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 {
- private final Map<String, Long> localStatistics = new HashMap<>();
-
- protected LeastLoadedCandidateSelectionStrategy(final long selectionDelayInMillis,
- final Map<String, Long> initialStatistics) {
- super(selectionDelayInMillis, initialStatistics);
-
- localStatistics.putAll(initialStatistics);
- }
-
- @Override
- public String newOwner(final String currentOwner, final Collection<String> viableCandidates) {
- checkArgument(viableCandidates.size() > 0);
- String leastLoadedCandidate = null;
- long leastLoadedCount = Long.MAX_VALUE;
-
- if (!Strings.isNullOrEmpty(currentOwner)) {
- long localVal = requireNonNullElse(localStatistics.get(currentOwner), 0L);
- localStatistics.put(currentOwner, localVal - 1);
- }
-
- for (String candidateName : viableCandidates) {
- long val = requireNonNullElse(localStatistics.get(candidateName), 0L);
- if (val < leastLoadedCount) {
- leastLoadedCount = val;
- leastLoadedCandidate = candidateName;
- }
- }
-
- if (leastLoadedCandidate == null) {
- leastLoadedCandidate = viableCandidates.iterator().next();
- }
-
- localStatistics.put(leastLoadedCandidate, leastLoadedCount + 1);
- return leastLoadedCandidate;
- }
-
- @VisibleForTesting
- Map<String, Long> getLocalStatistics() {
- return localStatistics;
- }
-}
+++ /dev/null
-module entity-owners {
- yang-version 1;
- namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:clustering:entity-owners";
- prefix "entity-owners";
-
- description
- "This module contains the base YANG definitions for
- an implementation of the EntityOwnershipService which stores
- entity ownership information in the data store";
-
- revision "2015-08-04" {
- description "Initial revision.";
- }
-
- container entity-owners {
-
- // A list of all entities grouped by type
- list entity-type {
- key type;
- leaf type {
- type string;
- }
-
- list entity {
- key id;
-
- leaf id {
- type instance-identifier;
- }
-
- leaf owner {
- type string;
- }
-
- // A list of all the candidates that would like to own the entity
- list candidate {
- key name;
- ordered-by user;
-
- leaf name {
- type string;
- }
- }
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2016 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.entityownership;
-
-import akka.actor.ActorSystem;
-import akka.testkit.javadsl.TestKit;
-import com.typesafe.config.ConfigFactory;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class AbstractClusterRefEntityOwnershipTest extends AbstractEntityOwnershipTest {
-
- private static ActorSystem system;
-
- @BeforeClass
- public static void setUpClass() {
- system = ActorSystem.create("test", ConfigFactory.load().getConfig("test-config"));
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestKit.shutdownActorSystem(system);
- system = null;
- }
-
- protected static ActorSystem getSystem() {
- return system;
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.CANDIDATE_NAME_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_ID_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNER_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_TYPE_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.candidatePath;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-
-import akka.pattern.Patterns;
-import akka.testkit.TestActorRef;
-import akka.util.Timeout;
-import com.google.common.base.Stopwatch;
-import com.google.common.util.concurrent.Uninterruptibles;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.datastore.AbstractActorTest;
-import org.opendaylight.controller.cluster.datastore.AbstractShardTest;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier;
-import org.opendaylight.controller.cluster.raft.client.messages.GetOnDemandRaftState;
-import org.opendaylight.controller.cluster.raft.client.messages.OnDemandRaftState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.EntityOwners;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.EntityType;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.entity.type.entity.Candidate;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import scala.concurrent.Await;
-import scala.concurrent.Future;
-import scala.concurrent.duration.FiniteDuration;
-
-/**
- * Abstract base class providing utility methods.
- *
- * @author Thomas Pantelis
- */
-public class AbstractEntityOwnershipTest extends AbstractActorTest {
- protected final Logger testLog = LoggerFactory.getLogger(getClass());
-
- private static final AtomicInteger NEXT_SHARD_NUM = new AtomicInteger();
-
- protected void verifyEntityCandidate(final NormalizedNode node, final String entityType,
- final YangInstanceIdentifier entityId, final String candidateName, final boolean expectPresent) {
- try {
- assertNotNull("Missing " + EntityOwners.QNAME.toString(), node);
- assertTrue(node instanceof ContainerNode);
-
- ContainerNode entityOwnersNode = (ContainerNode) node;
-
- MapEntryNode entityTypeEntry = getMapEntryNodeChild(entityOwnersNode, EntityType.QNAME,
- ENTITY_TYPE_QNAME, entityType, true);
-
- MapEntryNode entityEntry = getMapEntryNodeChild(entityTypeEntry, ENTITY_QNAME, ENTITY_ID_QNAME,
- entityId, true);
-
- getMapEntryNodeChild(entityEntry, Candidate.QNAME, CANDIDATE_NAME_QNAME, candidateName, expectPresent);
- } catch (AssertionError e) {
- throw new AssertionError("Verification of entity candidate failed - returned data was: " + node, e);
- }
- }
-
- protected void verifyEntityCandidate(final String entityType, final YangInstanceIdentifier entityId,
- final String candidateName, final Function<YangInstanceIdentifier,NormalizedNode> reader,
- final boolean expectPresent) {
- AssertionError lastError = null;
- Stopwatch sw = Stopwatch.createStarted();
- while (sw.elapsed(TimeUnit.MILLISECONDS) <= 5000) {
- NormalizedNode node = reader.apply(ENTITY_OWNERS_PATH);
- try {
- verifyEntityCandidate(node, entityType, entityId, candidateName, expectPresent);
- return;
- } catch (AssertionError e) {
- lastError = e;
- Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
- }
- }
-
- throw lastError;
- }
-
- protected void verifyEntityCandidate(final String entityType, final YangInstanceIdentifier entityId,
- final String candidateName, final Function<YangInstanceIdentifier,NormalizedNode> reader) {
- verifyEntityCandidate(entityType, entityId, candidateName, reader, true);
- }
-
- protected MapEntryNode getMapEntryNodeChild(final DataContainerNode parent,
- final QName childMap, final QName child, final Object key, final boolean expectPresent) {
- Optional<DataContainerChild> childNode = parent.findChildByArg(new NodeIdentifier(childMap));
- // We have to account for empty maps disappearing. If we expect the entry to be non-present, tolerate a missing
- // map.
- if (!expectPresent && !childNode.isPresent()) {
- return null;
- }
-
- assertTrue("Missing " + childMap.toString(), childNode.isPresent());
-
- MapNode entityTypeMapNode = (MapNode) childNode.get();
- Optional<MapEntryNode> entityTypeEntry = entityTypeMapNode.findChildByArg(NodeIdentifierWithPredicates.of(
- childMap, child, key));
- if (expectPresent && !entityTypeEntry.isPresent()) {
- fail("Missing " + childMap.toString() + " entry for " + key + ". Actual: " + entityTypeMapNode.body());
- } else if (!expectPresent && entityTypeEntry.isPresent()) {
- fail("Found unexpected " + childMap.toString() + " entry for " + key);
- }
-
- return entityTypeEntry.isPresent() ? entityTypeEntry.get() : null;
- }
-
- static void verifyOwner(final String expected, final String entityType, final YangInstanceIdentifier entityId,
- final Function<YangInstanceIdentifier,NormalizedNode> reader) {
- AssertionError lastError = null;
- YangInstanceIdentifier entityPath = entityPath(entityType, entityId).node(ENTITY_OWNER_QNAME);
- Stopwatch sw = Stopwatch.createStarted();
- while (sw.elapsed(TimeUnit.MILLISECONDS) <= 5000) {
- try {
- NormalizedNode node = reader.apply(entityPath);
- assertNotNull("Owner was not set for entityId: " + entityId, node);
- assertEquals("Entity owner", expected, node.body().toString());
- return;
- } catch (AssertionError e) {
- lastError = e;
- Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
- }
- }
-
- throw lastError;
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- static void verifyOwner(final TestActorRef<? extends EntityOwnershipShard> shard, final String entityType,
- final YangInstanceIdentifier entityId, final String localMemberName) {
- verifyOwner(localMemberName, entityType, entityId, path -> {
- try {
- return AbstractShardTest.readStore(shard, path);
- } catch (Exception e) {
- return null;
- }
- });
- }
-
- protected void verifyNodeRemoved(final YangInstanceIdentifier path,
- final Function<YangInstanceIdentifier,NormalizedNode> reader) {
- AssertionError lastError = null;
- Stopwatch sw = Stopwatch.createStarted();
- while (sw.elapsed(TimeUnit.MILLISECONDS) <= 5000) {
- try {
- assertNull("Node was not removed at path: " + path, reader.apply(path));
- return;
- } catch (AssertionError e) {
- lastError = e;
- Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
- }
- }
-
- throw lastError;
- }
-
- static void writeNode(final YangInstanceIdentifier path, final NormalizedNode node,
- final ShardDataTree shardDataTree) throws DataValidationFailedException {
- DataTreeModification modification = shardDataTree.newModification();
- modification.merge(path, node);
- commit(shardDataTree, modification);
- }
-
- static void deleteNode(final YangInstanceIdentifier path, final ShardDataTree shardDataTree)
- throws DataValidationFailedException {
- DataTreeModification modification = shardDataTree.newModification();
- modification.delete(path);
- commit(shardDataTree, modification);
- }
-
- static void commit(final ShardDataTree shardDataTree, final DataTreeModification modification)
- throws DataValidationFailedException {
- modification.ready();
- shardDataTree.getDataTree().validate(modification);
- final DataTreeCandidate candidate = shardDataTree.getDataTree().prepare(modification);
- shardDataTree.getDataTree().commit(candidate);
- shardDataTree.notifyListeners(candidate);
- }
-
- static DOMEntityOwnershipChange ownershipChange(final DOMEntity expEntity, final boolean expWasOwner,
- final boolean expIsOwner, final boolean expHasOwner) {
- return ownershipChange(expEntity, expWasOwner, expIsOwner, expHasOwner, false);
- }
-
- static DOMEntityOwnershipChange ownershipChange(final DOMEntity expEntity, final boolean expWasOwner,
- final boolean expIsOwner, final boolean expHasOwner, final boolean expInJeopardy) {
- return argThat(change -> expEntity.equals(change.getEntity()) && expWasOwner == change.getState().wasOwner()
- && expIsOwner == change.getState().isOwner() && expHasOwner == change.getState().hasOwner()
- && expInJeopardy == change.inJeopardy());
- }
-
- static DOMEntityOwnershipChange ownershipChange(final DOMEntity expEntity) {
- return argThat(change -> expEntity.equals(change.getEntity()));
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- static void verifyNoOwnerSet(final TestActorRef<? extends EntityOwnershipShard> shard, final String entityType,
- final YangInstanceIdentifier entityId) {
- YangInstanceIdentifier entityPath = entityPath(entityType, entityId).node(ENTITY_OWNER_QNAME);
- try {
- NormalizedNode node = AbstractShardTest.readStore(shard, entityPath);
- if (node != null) {
- fail("Owner " + node.body() + " was set for " + entityPath);
- }
-
- } catch (Exception e) {
- throw new AssertionError("read failed", e);
- }
- }
-
- static void verifyRaftState(final TestActorRef<? extends EntityOwnershipShard> shard,
- final Consumer<OnDemandRaftState> verifier)
- throws Exception {
- AssertionError lastError = null;
- Stopwatch sw = Stopwatch.createStarted();
- while (sw.elapsed(TimeUnit.SECONDS) <= 5) {
- FiniteDuration operationDuration = FiniteDuration.create(5, TimeUnit.SECONDS);
- Future<Object> future = Patterns.ask(shard, GetOnDemandRaftState.INSTANCE, new Timeout(operationDuration));
- OnDemandRaftState raftState = (OnDemandRaftState)Await.result(future, operationDuration);
- try {
- verifier.accept(raftState);
- return;
- } catch (AssertionError e) {
- lastError = e;
- Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS);
- }
- }
-
- throw lastError;
- }
-
- static ShardIdentifier newShardId(final String memberName) {
- return ShardIdentifier.create("entity-ownership", MemberName.forName(memberName),
- "operational" + NEXT_SHARD_NUM.getAndIncrement());
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- void verifyEntityCandidateRemoved(final TestActorRef<EntityOwnershipShard> shard, final String entityType,
- final YangInstanceIdentifier entityId, final String candidateName) {
- verifyNodeRemoved(candidatePath(entityType, entityId, candidateName), path -> {
- try {
- return AbstractShardTest.readStore(shard, path);
- } catch (Exception e) {
- throw new AssertionError("Failed to read " + path, e);
- }
- });
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- void verifyCommittedEntityCandidate(final TestActorRef<? extends EntityOwnershipShard> shard,
- final String entityType, final YangInstanceIdentifier entityId, final String candidateName) {
- verifyEntityCandidate(entityType, entityId, candidateName, path -> {
- try {
- return AbstractShardTest.readStore(shard, path);
- } catch (Exception e) {
- throw new AssertionError("Failed to read " + path, e);
- }
- });
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- void verifyNoEntityCandidate(final TestActorRef<? extends EntityOwnershipShard> shard, final String entityType,
- final YangInstanceIdentifier entityId, final String candidateName) {
- verifyEntityCandidate(entityType, entityId, candidateName, path -> {
- try {
- return AbstractShardTest.readStore(shard, path);
- } catch (Exception e) {
- throw new AssertionError("Failed to read " + path, e);
- }
- }, false);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.junit.Assert.assertEquals;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.candidatePath;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityOwnersWithCandidate;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-
-import akka.testkit.javadsl.TestKit;
-import com.google.common.collect.ImmutableSet;
-import java.time.Duration;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.opendaylight.controller.cluster.datastore.AbstractActorTest;
-import org.opendaylight.controller.cluster.datastore.Shard;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateAdded;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateRemoved;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
-
-/**
- * Unit tests for CandidateListChangeListener.
- *
- * @author Thomas Pantelis
- */
-public class CandidateListChangeListenerTest extends AbstractActorTest {
- private static final String ENTITY_TYPE = "test";
- private static final YangInstanceIdentifier ENTITY_ID1 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity1"));
- private static final YangInstanceIdentifier ENTITY_ID2 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity2"));
-
- private ShardDataTree shardDataTree;
-
- @Mock
- private Shard mockShard;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- shardDataTree = new ShardDataTree(mockShard, EOSTestUtils.SCHEMA_CONTEXT, TreeType.OPERATIONAL);
- }
-
- @Test
- public void testOnDataTreeChanged() throws Exception {
- TestKit kit = new TestKit(getSystem());
-
- new CandidateListChangeListener(kit.getRef(), "test").init(shardDataTree);
-
- String memberName1 = "member-1";
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, memberName1));
-
- CandidateAdded candidateAdded = kit.expectMsgClass(CandidateAdded.class);
- assertEquals("getEntityId", entityPath(ENTITY_TYPE, ENTITY_ID1), candidateAdded.getEntityPath());
- assertEquals("getNewCandidate", memberName1, candidateAdded.getNewCandidate());
- assertEquals("getAllCandidates", ImmutableSet.of(memberName1),
- ImmutableSet.copyOf(candidateAdded.getAllCandidates()));
-
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, memberName1));
- kit.expectNoMessage(Duration.ofMillis(500));
-
- String memberName2 = "member-2";
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, memberName2));
-
- candidateAdded = kit.expectMsgClass(CandidateAdded.class);
- assertEquals("getEntityId", entityPath(ENTITY_TYPE, ENTITY_ID1), candidateAdded.getEntityPath());
- assertEquals("getNewCandidate", memberName2, candidateAdded.getNewCandidate());
- assertEquals("getAllCandidates", ImmutableSet.of(memberName1, memberName2),
- ImmutableSet.copyOf(candidateAdded.getAllCandidates()));
-
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID2, memberName1));
-
- candidateAdded = kit.expectMsgClass(CandidateAdded.class);
- assertEquals("getEntityId", entityPath(ENTITY_TYPE, ENTITY_ID2), candidateAdded.getEntityPath());
- assertEquals("getNewCandidate", memberName1, candidateAdded.getNewCandidate());
- assertEquals("getAllCandidates", ImmutableSet.of(memberName1),
- ImmutableSet.copyOf(candidateAdded.getAllCandidates()));
-
- deleteNode(candidatePath(ENTITY_TYPE, ENTITY_ID1, memberName1));
-
- CandidateRemoved candidateRemoved = kit.expectMsgClass(CandidateRemoved.class);
- assertEquals("getEntityId", entityPath(ENTITY_TYPE, ENTITY_ID1), candidateRemoved.getEntityPath());
- assertEquals("getRemovedCandidate", memberName1, candidateRemoved.getRemovedCandidate());
- assertEquals("getRemainingCandidates", ImmutableSet.of(memberName2),
- ImmutableSet.copyOf(candidateRemoved.getRemainingCandidates()));
-
- deleteNode(candidatePath(ENTITY_TYPE, ENTITY_ID1, memberName2));
-
- candidateRemoved = kit.expectMsgClass(CandidateRemoved.class);
- assertEquals("getEntityId", entityPath(ENTITY_TYPE, ENTITY_ID1), candidateRemoved.getEntityPath());
- assertEquals("getRemovedCandidate", memberName2, candidateRemoved.getRemovedCandidate());
- assertEquals("getRemainingCandidates", ImmutableSet.of(),
- ImmutableSet.copyOf(candidateRemoved.getRemainingCandidates()));
- }
-
- private void writeNode(final YangInstanceIdentifier path, final NormalizedNode node)
- throws DataValidationFailedException {
- AbstractEntityOwnershipTest.writeNode(path, node, shardDataTree);
- }
-
- private void deleteNode(final YangInstanceIdentifier path) throws DataValidationFailedException {
- AbstractEntityOwnershipTest.deleteNode(path, shardDataTree);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.AdditionalMatchers.or;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.opendaylight.controller.cluster.datastore.MemberNode.verifyRaftState;
-import static org.opendaylight.controller.cluster.entityownership.AbstractEntityOwnershipTest.ownershipChange;
-import static org.opendaylight.controller.cluster.entityownership.DistributedEntityOwnershipService.ENTITY_OWNERSHIP_SHARD_NAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.CANDIDATE_NAME_NODE_ID;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-
-import akka.actor.ActorRef;
-import akka.actor.Status.Failure;
-import akka.actor.Status.Success;
-import akka.cluster.Cluster;
-import akka.pattern.Patterns;
-import akka.util.Timeout;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Uninterruptibles;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.exceptions.base.MockitoException;
-import org.opendaylight.controller.cluster.datastore.AbstractDataStore;
-import org.opendaylight.controller.cluster.datastore.DatastoreContext;
-import org.opendaylight.controller.cluster.datastore.IntegrationTestKit;
-import org.opendaylight.controller.cluster.datastore.MemberNode;
-import org.opendaylight.controller.cluster.datastore.messages.AddShardReplica;
-import org.opendaylight.controller.cluster.datastore.messages.ChangeShardMembersVotingStatus;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig;
-import org.opendaylight.controller.cluster.raft.policy.DisableElectionsRaftPolicy;
-import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal;
-import org.opendaylight.controller.cluster.raft.utils.InMemorySnapshotStore;
-import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.clustering.entity.owners.rev150804.entity.owners.entity.type.entity.Candidate;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import scala.concurrent.Await;
-import scala.concurrent.Future;
-import scala.concurrent.duration.FiniteDuration;
-
-/**
- * End-to-end integration tests for the entity ownership functionality.
- *
- * @author Thomas Pantelis
- */
-public class DistributedEntityOwnershipIntegrationTest {
- private static final String MODULE_SHARDS_CONFIG = "module-shards-default.conf";
- private static final String MODULE_SHARDS_5_NODE_CONFIG = "module-shards-default-5-node.conf";
- private static final String MODULE_SHARDS_MEMBER_1_CONFIG = "module-shards-default-member-1.conf";
- private static final String ENTITY_TYPE1 = "entityType1";
- private static final String ENTITY_TYPE2 = "entityType2";
- private static final DOMEntity ENTITY1 = new DOMEntity(ENTITY_TYPE1, "entity1");
- private static final DOMEntity ENTITY1_2 = new DOMEntity(ENTITY_TYPE2, "entity1");
- private static final DOMEntity ENTITY2 = new DOMEntity(ENTITY_TYPE1, "entity2");
- private static final DOMEntity ENTITY3 = new DOMEntity(ENTITY_TYPE1, "entity3");
- private static final DOMEntity ENTITY4 = new DOMEntity(ENTITY_TYPE1, "entity4");
- private final DatastoreContext.Builder leaderDatastoreContextBuilder =
- DatastoreContext.newBuilder().shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(5)
- .shardIsolatedLeaderCheckIntervalInMillis(1000000);
-
- private final DatastoreContext.Builder followerDatastoreContextBuilder =
- DatastoreContext.newBuilder().shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(10000);
-
- private final List<MemberNode> memberNodes = new ArrayList<>();
-
- @Mock
- private DOMEntityOwnershipListener leaderMockListener;
-
- @Mock
- private DOMEntityOwnershipListener leaderMockListener2;
-
- @Mock
- private DOMEntityOwnershipListener follower1MockListener;
-
- @Mock
- private DOMEntityOwnershipListener follower2MockListener;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- InMemoryJournal.clear();
- InMemorySnapshotStore.clear();
- }
-
- @After
- public void tearDown() {
- for (MemberNode m : Lists.reverse(memberNodes)) {
- m.cleanup();
- }
- memberNodes.clear();
- }
-
- private static DistributedEntityOwnershipService newOwnershipService(final AbstractDataStore datastore) {
- return DistributedEntityOwnershipService.start(datastore.getActorUtils(),
- EntityOwnerSelectionStrategyConfig.newBuilder().build());
- }
-
- @Test
- public void testFunctionalityWithThreeNodes() throws Exception {
- String name = "testFunctionalityWithThreeNodes";
- MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- follower1Node.configDataStore().waitTillReady();
- follower2Node.configDataStore().waitTillReady();
-
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
- final DOMEntityOwnershipService follower1EntityOwnershipService =
- newOwnershipService(follower1Node.configDataStore());
- final DOMEntityOwnershipService follower2EntityOwnershipService =
- newOwnershipService(follower2Node.configDataStore());
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- leaderEntityOwnershipService.registerListener(ENTITY_TYPE1, leaderMockListener);
- leaderEntityOwnershipService.registerListener(ENTITY_TYPE2, leaderMockListener2);
- follower1EntityOwnershipService.registerListener(ENTITY_TYPE1, follower1MockListener);
-
- // Register leader candidate for entity1 and verify it becomes owner
-
- leaderEntityOwnershipService.registerCandidate(ENTITY1);
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, true, true));
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, false, true));
- reset(leaderMockListener, follower1MockListener);
-
- verifyGetOwnershipState(leaderEntityOwnershipService, ENTITY1, EntityOwnershipState.IS_OWNER);
- verifyGetOwnershipState(follower1EntityOwnershipService, ENTITY1, EntityOwnershipState.OWNED_BY_OTHER);
-
- // Register leader candidate for entity1_2 (same id, different type) and verify it becomes owner
-
- leaderEntityOwnershipService.registerCandidate(ENTITY1_2);
- verify(leaderMockListener2, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1_2, false, true, true));
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(leaderMockListener, never()).ownershipChanged(ownershipChange(ENTITY1_2));
- reset(leaderMockListener2);
-
- // Register follower1 candidate for entity1 and verify it gets added but doesn't become owner
-
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-1", "member-2");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-1");
- verifyOwner(follower2Node.configDataStore(), ENTITY1, "member-1");
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(leaderMockListener, never()).ownershipChanged(ownershipChange(ENTITY1));
- verify(follower1MockListener, never()).ownershipChanged(ownershipChange(ENTITY1));
-
- // Register follower1 candidate for entity2 and verify it becomes owner
-
- final DOMEntityOwnershipCandidateRegistration follower1Entity2Reg =
- follower1EntityOwnershipService.registerCandidate(ENTITY2);
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, false, true, true));
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, false, false, true));
- verifyOwner(follower2Node.configDataStore(), ENTITY2, "member-2");
- reset(leaderMockListener, follower1MockListener);
-
- // Register follower2 candidate for entity2 and verify it gets added but doesn't become owner
-
- follower2EntityOwnershipService.registerListener(ENTITY_TYPE1, follower2MockListener);
- verify(follower2MockListener, timeout(5000).times(2)).ownershipChanged(or(
- ownershipChange(ENTITY1, false, false, true), ownershipChange(ENTITY2, false, false, true)));
-
- follower2EntityOwnershipService.registerCandidate(ENTITY2);
- verifyCandidates(leaderDistributedDataStore, ENTITY2, "member-2", "member-3");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-2");
-
- // Unregister follower1 candidate for entity2 and verify follower2 becomes owner
-
- follower1Entity2Reg.close();
- verifyCandidates(leaderDistributedDataStore, ENTITY2, "member-3");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-3");
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, true, false, true));
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, false, false, true));
-
- // Depending on timing, follower2MockListener could get ownershipChanged with "false, false, true" if
- // if the original ownership change with "member-2 is replicated to follower2 after the listener is
- // registered.
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verify(follower2MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, false, true, true));
-
- // Register follower1 candidate for entity3 and verify it becomes owner
-
- follower1EntityOwnershipService.registerCandidate(ENTITY3);
- verifyOwner(leaderDistributedDataStore, ENTITY3, "member-2");
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY3, false, true, true));
- verify(follower2MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY3, false, false, true));
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY3, false, false, true));
-
- // Register follower2 candidate for entity4 and verify it becomes owner
-
- follower2EntityOwnershipService.registerCandidate(ENTITY4);
- verifyOwner(leaderDistributedDataStore, ENTITY4, "member-3");
- verify(follower2MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY4, false, true, true));
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY4, false, false, true));
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY4, false, false, true));
- reset(follower1MockListener, follower2MockListener);
-
- // Register follower1 candidate for entity4 and verify it gets added but doesn't become owner
-
- follower1EntityOwnershipService.registerCandidate(ENTITY4);
- verifyCandidates(leaderDistributedDataStore, ENTITY4, "member-3", "member-2");
- verifyOwner(leaderDistributedDataStore, ENTITY4, "member-3");
-
- // Shutdown follower2 and verify it's owned entities (entity 4) get re-assigned
-
- reset(leaderMockListener, follower1MockListener);
- follower2Node.cleanup();
-
- verify(follower1MockListener, timeout(15000)).ownershipChanged(ownershipChange(ENTITY4, false, true, true));
- verify(leaderMockListener, timeout(15000)).ownershipChanged(ownershipChange(ENTITY4, false, false, true));
-
- // Register leader candidate for entity2 and verify it becomes owner
-
- DOMEntityOwnershipCandidateRegistration leaderEntity2Reg =
- leaderEntityOwnershipService.registerCandidate(ENTITY2);
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-1");
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, false, true, true));
-
- // Unregister leader candidate for entity2 and verify the owner is cleared
-
- leaderEntity2Reg.close();
- verifyOwner(leaderDistributedDataStore, ENTITY2, "");
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, true, false, false));
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY2, false, false, false));
- }
-
- @Test
- public void testLeaderEntityOwnersReassignedAfterShutdown() throws Exception {
- followerDatastoreContextBuilder.shardElectionTimeoutFactor(5)
- .customRaftPolicyImplementation(DisableElectionsRaftPolicy.class.getName());
-
- String name = "testLeaderEntityOwnersReassignedAfterShutdown";
- MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- follower1Node.configDataStore().waitTillReady();
- follower2Node.configDataStore().waitTillReady();
-
- follower1Node.waitForMembersUp("member-1", "member-3");
-
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
- final DOMEntityOwnershipService follower1EntityOwnershipService =
- newOwnershipService(follower1Node.configDataStore());
- final DOMEntityOwnershipService follower2EntityOwnershipService =
- newOwnershipService(follower2Node.configDataStore());
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- // Register follower1 candidate for entity1 and verify it becomes owner
-
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-2");
-
- // Register leader candidate for entity1
-
- leaderEntityOwnershipService.registerCandidate(ENTITY1);
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-2", "member-1");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-2");
-
- // Register leader candidate for entity2 and verify it becomes owner
-
- leaderEntityOwnershipService.registerCandidate(ENTITY2);
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-1");
-
- // Register follower2 candidate for entity2
-
- follower2EntityOwnershipService.registerCandidate(ENTITY2);
- verifyCandidates(leaderDistributedDataStore, ENTITY2, "member-1", "member-3");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-1");
-
- // Re-enable elections on all remaining followers so one becomes the new leader
-
- ActorRef follower1Shard = IntegrationTestKit.findLocalShard(follower1Node.configDataStore().getActorUtils(),
- ENTITY_OWNERSHIP_SHARD_NAME);
- follower1Shard.tell(DatastoreContext.newBuilderFrom(followerDatastoreContextBuilder.build())
- .customRaftPolicyImplementation(null).build(), ActorRef.noSender());
-
- ActorRef follower2Shard = IntegrationTestKit.findLocalShard(follower2Node.configDataStore().getActorUtils(),
- ENTITY_OWNERSHIP_SHARD_NAME);
- follower2Shard.tell(DatastoreContext.newBuilderFrom(followerDatastoreContextBuilder.build())
- .customRaftPolicyImplementation(null).build(), ActorRef.noSender());
-
- // Shutdown the leader and verify its removed from the candidate list
-
- leaderNode.cleanup();
- follower1Node.waitForMemberDown("member-1");
- follower2Node.waitForMemberDown("member-1");
-
- // Verify the prior leader's entity owners are re-assigned.
-
- verifyCandidates(follower1Node.configDataStore(), ENTITY1, "member-2", "member-1");
- verifyCandidates(follower1Node.configDataStore(), ENTITY2, "member-1", "member-3");
- verifyOwner(follower1Node.configDataStore(), ENTITY1, "member-2");
- verifyOwner(follower1Node.configDataStore(), ENTITY2, "member-3");
- }
-
- @Test
- public void testLeaderAndFollowerEntityOwnersReassignedAfterShutdown() throws Exception {
- followerDatastoreContextBuilder.shardElectionTimeoutFactor(5)
- .customRaftPolicyImplementation(DisableElectionsRaftPolicy.class.getName());
-
- String name = "testLeaderAndFollowerEntityOwnersReassignedAfterShutdown";
- final MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1")
- .useAkkaArtery(false).testName(name)
- .moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG)
- .schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- final MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2")
- .testName(name).moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG)
- .useAkkaArtery(false).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- final MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3")
- .testName(name).moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG)
- .useAkkaArtery(false).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- final MemberNode follower3Node = MemberNode.builder(memberNodes).akkaConfig("Member4")
- .testName(name).moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG)
- .useAkkaArtery(false).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- final MemberNode follower4Node = MemberNode.builder(memberNodes).akkaConfig("Member5")
- .testName(name).moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG)
- .useAkkaArtery(false).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- follower1Node.configDataStore().waitTillReady();
- follower2Node.configDataStore().waitTillReady();
- follower3Node.configDataStore().waitTillReady();
- follower4Node.configDataStore().waitTillReady();
-
- leaderNode.waitForMembersUp("member-2", "member-3", "member-4", "member-5");
- follower1Node.waitForMembersUp("member-1", "member-3", "member-4", "member-5");
-
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
- final DOMEntityOwnershipService follower1EntityOwnershipService =
- newOwnershipService(follower1Node.configDataStore());
- final DOMEntityOwnershipService follower2EntityOwnershipService =
- newOwnershipService(follower2Node.configDataStore());
- final DOMEntityOwnershipService follower3EntityOwnershipService =
- newOwnershipService(follower3Node.configDataStore());
- newOwnershipService(follower4Node.configDataStore());
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- // Register follower1 candidate for entity1 and verify it becomes owner
-
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-2");
-
- // Register leader candidate for entity1
-
- leaderEntityOwnershipService.registerCandidate(ENTITY1);
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-2", "member-1");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-2");
-
- // Register leader candidate for entity2 and verify it becomes owner
-
- leaderEntityOwnershipService.registerCandidate(ENTITY2);
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-1");
-
- // Register follower2 candidate for entity2
-
- follower2EntityOwnershipService.registerCandidate(ENTITY2);
- verifyCandidates(leaderDistributedDataStore, ENTITY2, "member-1", "member-3");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-1");
-
- // Register follower3 as a candidate for entity2 as well
-
- follower3EntityOwnershipService.registerCandidate(ENTITY2);
- verifyCandidates(leaderDistributedDataStore, ENTITY2, "member-1", "member-3", "member-4");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-1");
-
- // Re-enable elections on all remaining followers so one becomes the new leader
-
- ActorRef follower1Shard = IntegrationTestKit.findLocalShard(follower1Node.configDataStore().getActorUtils(),
- ENTITY_OWNERSHIP_SHARD_NAME);
- follower1Shard.tell(DatastoreContext.newBuilderFrom(followerDatastoreContextBuilder.build())
- .customRaftPolicyImplementation(null).build(), ActorRef.noSender());
-
- ActorRef follower2Shard = IntegrationTestKit.findLocalShard(follower2Node.configDataStore().getActorUtils(),
- ENTITY_OWNERSHIP_SHARD_NAME);
- follower2Shard.tell(DatastoreContext.newBuilderFrom(followerDatastoreContextBuilder.build())
- .customRaftPolicyImplementation(null).build(), ActorRef.noSender());
-
- ActorRef follower4Shard = IntegrationTestKit.findLocalShard(follower4Node.configDataStore().getActorUtils(),
- ENTITY_OWNERSHIP_SHARD_NAME);
- follower4Shard.tell(DatastoreContext.newBuilderFrom(followerDatastoreContextBuilder.build())
- .customRaftPolicyImplementation(null).build(), ActorRef.noSender());
-
- // Shutdown the leader and follower3
-
- leaderNode.cleanup();
-
- follower1Node.waitForMemberDown("member-1");
- follower2Node.waitForMemberDown("member-1");
- follower4Node.waitForMemberDown("member-1");
-
- follower3Node.cleanup();
-
- follower1Node.waitForMemberDown("member-4");
- follower2Node.waitForMemberDown("member-4");
- follower4Node.waitForMemberDown("member-4");
-
- // Verify the prior leader's and follower3 entity owners are re-assigned.
-
- verifyCandidates(follower1Node.configDataStore(), ENTITY1, "member-2", "member-1");
- verifyCandidates(follower1Node.configDataStore(), ENTITY2, "member-1", "member-3", "member-4");
- verifyOwner(follower1Node.configDataStore(), ENTITY1, "member-2");
- verifyOwner(follower1Node.configDataStore(), ENTITY2, "member-3");
- }
-
- /**
- * Reproduces bug <a href="https://bugs.opendaylight.org/show_bug.cgi?id=4554">4554</a>.
- */
- @Test
- public void testCloseCandidateRegistrationInQuickSuccession() throws Exception {
- String name = "testCloseCandidateRegistrationInQuickSuccession";
- MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- follower1Node.configDataStore().waitTillReady();
- follower2Node.configDataStore().waitTillReady();
-
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
- final DOMEntityOwnershipService follower1EntityOwnershipService =
- newOwnershipService(follower1Node.configDataStore());
- final DOMEntityOwnershipService follower2EntityOwnershipService =
- newOwnershipService(follower2Node.configDataStore());
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- leaderEntityOwnershipService.registerListener(ENTITY_TYPE1, leaderMockListener);
- follower1EntityOwnershipService.registerListener(ENTITY_TYPE1, follower1MockListener);
- follower2EntityOwnershipService.registerListener(ENTITY_TYPE1, follower2MockListener);
-
- final DOMEntityOwnershipCandidateRegistration candidate1 =
- leaderEntityOwnershipService.registerCandidate(ENTITY1);
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, true, true));
-
- final DOMEntityOwnershipCandidateRegistration candidate2 =
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- verify(follower1MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, false, true));
-
- final DOMEntityOwnershipCandidateRegistration candidate3 =
- follower2EntityOwnershipService.registerCandidate(ENTITY1);
- verify(follower2MockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, false, true));
-
- Mockito.reset(leaderMockListener, follower1MockListener, follower2MockListener);
-
- ArgumentCaptor<DOMEntityOwnershipChange> leaderChangeCaptor =
- ArgumentCaptor.forClass(DOMEntityOwnershipChange.class);
- ArgumentCaptor<DOMEntityOwnershipChange> follower1ChangeCaptor =
- ArgumentCaptor.forClass(DOMEntityOwnershipChange.class);
- ArgumentCaptor<DOMEntityOwnershipChange> follower2ChangeCaptor =
- ArgumentCaptor.forClass(DOMEntityOwnershipChange.class);
- doNothing().when(leaderMockListener).ownershipChanged(leaderChangeCaptor.capture());
- doNothing().when(follower1MockListener).ownershipChanged(follower1ChangeCaptor.capture());
- doNothing().when(follower2MockListener).ownershipChanged(follower2ChangeCaptor.capture());
-
- candidate1.close();
- candidate2.close();
- candidate3.close();
-
- boolean passed = false;
- for (int i = 0; i < 100; i++) {
- Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS);
- final Optional<EntityOwnershipState> leaderState = leaderEntityOwnershipService.getOwnershipState(ENTITY1);
- final Optional<EntityOwnershipState> follower1State =
- follower1EntityOwnershipService.getOwnershipState(ENTITY1);
- final Optional<EntityOwnershipState> follower2State =
- follower2EntityOwnershipService.getOwnershipState(ENTITY1);
- final Optional<DOMEntityOwnershipChange> leaderChange = getValueSafely(leaderChangeCaptor);
- final Optional<DOMEntityOwnershipChange> follower1Change = getValueSafely(follower1ChangeCaptor);
- final Optional<DOMEntityOwnershipChange> follower2Change = getValueSafely(follower2ChangeCaptor);
- if (!leaderState.isPresent() || leaderState.get() == EntityOwnershipState.NO_OWNER
- && follower1State.isPresent() && follower1State.get() == EntityOwnershipState.NO_OWNER
- && follower2State.isPresent() && follower2State.get() == EntityOwnershipState.NO_OWNER
- && leaderChange.isPresent() && !leaderChange.get().getState().hasOwner()
- && follower1Change.isPresent() && !follower1Change.get().getState().hasOwner()
- && follower2Change.isPresent() && !follower2Change.get().getState().hasOwner()) {
- passed = true;
- break;
- }
- }
-
- assertTrue("No ownership change message was sent with hasOwner=false", passed);
- }
-
- private static Optional<DOMEntityOwnershipChange> getValueSafely(
- final ArgumentCaptor<DOMEntityOwnershipChange> captor) {
- try {
- return Optional.ofNullable(captor.getValue());
- } catch (MockitoException e) {
- // No value was captured
- return Optional.empty();
- }
- }
-
- /**
- * Tests bootstrapping the entity-ownership shard when there's no shards initially configured for local
- * member. The entity-ownership shard is initially created as inactive (ie remains a follower), requiring
- * an AddShardReplica request to join it to an existing leader.
- */
- @Test
- public void testEntityOwnershipShardBootstrapping() throws Exception {
- String name = "testEntityOwnershipShardBootstrapping";
- String moduleShardsConfig = MODULE_SHARDS_MEMBER_1_CONFIG;
- MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
- .moduleShardsConfig(moduleShardsConfig).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(moduleShardsConfig).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore follower1DistributedDataStore = follower1Node.configDataStore();
- follower1DistributedDataStore.waitTillReady();
-
- leaderNode.waitForMembersUp("member-2");
- follower1Node.waitForMembersUp("member-1");
-
- DOMEntityOwnershipService follower1EntityOwnershipService = newOwnershipService(follower1DistributedDataStore);
-
- leaderEntityOwnershipService.registerListener(ENTITY_TYPE1, leaderMockListener);
-
- // Register a candidate for follower1 - should get queued since follower1 has no leader
- final DOMEntityOwnershipCandidateRegistration candidateReg =
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(leaderMockListener, never()).ownershipChanged(ownershipChange(ENTITY1));
-
- // Add replica in follower1
- AddShardReplica addReplica = new AddShardReplica(ENTITY_OWNERSHIP_SHARD_NAME);
- follower1DistributedDataStore.getActorUtils().getShardManager().tell(addReplica,
- follower1Node.kit().getRef());
- Object reply = follower1Node.kit().expectMsgAnyClassOf(follower1Node.kit().duration("5 sec"),
- Success.class, Failure.class);
- if (reply instanceof Failure) {
- throw new AssertionError("AddShardReplica failed", ((Failure)reply).cause());
- }
-
- // The queued candidate registration should proceed
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, false, true));
- reset(leaderMockListener);
-
- candidateReg.close();
- verify(leaderMockListener, timeout(5000)).ownershipChanged(ownershipChange(ENTITY1, false, false, false));
- reset(leaderMockListener);
-
- // Restart follower1 and verify the entity ownership shard is re-instated by registering.
- Cluster.get(leaderNode.kit().getSystem()).down(Cluster.get(follower1Node.kit().getSystem()).selfAddress());
- follower1Node.cleanup();
-
- follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(moduleShardsConfig).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
- follower1EntityOwnershipService = newOwnershipService(follower1Node.configDataStore());
-
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- verify(leaderMockListener, timeout(20000)).ownershipChanged(ownershipChange(ENTITY1, false, false, true));
-
- verifyRaftState(follower1Node.configDataStore(), ENTITY_OWNERSHIP_SHARD_NAME, raftState -> {
- assertNull("Custom RaftPolicy class name", raftState.getCustomRaftPolicyClassName());
- assertEquals("Peer count", 1, raftState.getPeerAddresses().keySet().size());
- assertThat("Peer Id", Iterables.<String>getLast(raftState.getPeerAddresses().keySet()),
- org.hamcrest.CoreMatchers.containsString("member-1"));
- });
- }
-
- @Test
- public void testOwnerSelectedOnRapidUnregisteringAndRegisteringOfCandidates() throws Exception {
- String name = "testOwnerSelectedOnRapidUnregisteringAndRegisteringOfCandidates";
- MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- follower1Node.configDataStore().waitTillReady();
- follower2Node.configDataStore().waitTillReady();
-
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
- final DOMEntityOwnershipService follower1EntityOwnershipService =
- newOwnershipService(follower1Node.configDataStore());
- newOwnershipService(follower2Node.configDataStore());
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- // Register leader candidate for entity1 and verify it becomes owner
-
- DOMEntityOwnershipCandidateRegistration leaderEntity1Reg =
- leaderEntityOwnershipService.registerCandidate(ENTITY1);
-
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-1");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-1");
-
- leaderEntity1Reg.close();
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
-
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-2");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-2");
- }
-
- @Test
- public void testOwnerSelectedOnRapidRegisteringAndUnregisteringOfCandidates() throws Exception {
- String name = "testOwnerSelectedOnRapidRegisteringAndUnregisteringOfCandidates";
- MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3").testName(name)
- .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- follower1Node.configDataStore().waitTillReady();
- follower2Node.configDataStore().waitTillReady();
-
- final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
- final DOMEntityOwnershipService follower1EntityOwnershipService =
- newOwnershipService(follower1Node.configDataStore());
- newOwnershipService(follower2Node.configDataStore());
-
- leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorUtils(), ENTITY_OWNERSHIP_SHARD_NAME);
-
- // Register leader candidate for entity1 and verify it becomes owner
-
- final DOMEntityOwnershipCandidateRegistration leaderEntity1Reg =
- leaderEntityOwnershipService.registerCandidate(ENTITY1);
-
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-1");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-1");
-
- follower1EntityOwnershipService.registerCandidate(ENTITY1);
- leaderEntity1Reg.close();
-
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-2");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-2");
- }
-
- @Test
- public void testEntityOwnershipWithNonVotingMembers() throws Exception {
- followerDatastoreContextBuilder.shardElectionTimeoutFactor(5)
- .customRaftPolicyImplementation(DisableElectionsRaftPolicy.class.getName());
-
- String name = "testEntityOwnershipWithNonVotingMembers";
- final MemberNode member1LeaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1")
- .useAkkaArtery(false).testName(name)
- .moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(leaderDatastoreContextBuilder).build();
-
- final MemberNode member2FollowerNode = MemberNode.builder(memberNodes).akkaConfig("Member2")
- .useAkkaArtery(false).testName(name)
- .moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- final MemberNode member3FollowerNode = MemberNode.builder(memberNodes).akkaConfig("Member3")
- .useAkkaArtery(false).testName(name)
- .moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- final MemberNode member4FollowerNode = MemberNode.builder(memberNodes).akkaConfig("Member4")
- .useAkkaArtery(false).testName(name)
- .moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- final MemberNode member5FollowerNode = MemberNode.builder(memberNodes).akkaConfig("Member5")
- .useAkkaArtery(false).testName(name)
- .moduleShardsConfig(MODULE_SHARDS_5_NODE_CONFIG).schemaContext(EOSTestUtils.SCHEMA_CONTEXT)
- .createOperDatastore(false).datastoreContextBuilder(followerDatastoreContextBuilder).build();
-
- AbstractDataStore leaderDistributedDataStore = member1LeaderNode.configDataStore();
-
- leaderDistributedDataStore.waitTillReady();
- member2FollowerNode.configDataStore().waitTillReady();
- member3FollowerNode.configDataStore().waitTillReady();
- member4FollowerNode.configDataStore().waitTillReady();
- member5FollowerNode.configDataStore().waitTillReady();
-
- member1LeaderNode.waitForMembersUp("member-2", "member-3", "member-4", "member-5");
-
- final DOMEntityOwnershipService member3EntityOwnershipService =
- newOwnershipService(member3FollowerNode.configDataStore());
- final DOMEntityOwnershipService member4EntityOwnershipService =
- newOwnershipService(member4FollowerNode.configDataStore());
- final DOMEntityOwnershipService member5EntityOwnershipService =
- newOwnershipService(member5FollowerNode.configDataStore());
-
- newOwnershipService(member1LeaderNode.configDataStore());
- member1LeaderNode.kit().waitUntilLeader(member1LeaderNode.configDataStore().getActorUtils(),
- ENTITY_OWNERSHIP_SHARD_NAME);
-
- // Make member4 and member5 non-voting
-
- Future<Object> future = Patterns.ask(leaderDistributedDataStore.getActorUtils().getShardManager(),
- new ChangeShardMembersVotingStatus(ENTITY_OWNERSHIP_SHARD_NAME,
- ImmutableMap.of("member-4", Boolean.FALSE, "member-5", Boolean.FALSE)),
- new Timeout(10, TimeUnit.SECONDS));
- Object response = Await.result(future, FiniteDuration.apply(10, TimeUnit.SECONDS));
- if (response instanceof Throwable) {
- throw new AssertionError("ChangeShardMembersVotingStatus failed", (Throwable)response);
- }
-
- assertNull("Expected null Success response. Actual " + response, response);
-
- // Register member4 candidate for entity1 - it should not become owner since it's non-voting
-
- member4EntityOwnershipService.registerCandidate(ENTITY1);
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-4");
-
- // Register member5 candidate for entity2 - it should not become owner since it's non-voting
-
- member5EntityOwnershipService.registerCandidate(ENTITY2);
- verifyCandidates(leaderDistributedDataStore, ENTITY2, "member-5");
-
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verifyOwner(leaderDistributedDataStore, ENTITY1, "");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "");
-
- // Register member3 candidate for entity1 - it should become owner since it's voting
-
- member3EntityOwnershipService.registerCandidate(ENTITY1);
- verifyCandidates(leaderDistributedDataStore, ENTITY1, "member-4", "member-3");
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-3");
-
- // Switch member4 and member5 back to voting and member3 non-voting. This should result in member4 and member5
- // to become entity owners.
-
- future = Patterns.ask(leaderDistributedDataStore.getActorUtils().getShardManager(),
- new ChangeShardMembersVotingStatus(ENTITY_OWNERSHIP_SHARD_NAME,
- ImmutableMap.of("member-3", Boolean.FALSE, "member-4", Boolean.TRUE, "member-5", Boolean.TRUE)),
- new Timeout(10, TimeUnit.SECONDS));
- response = Await.result(future, FiniteDuration.apply(10, TimeUnit.SECONDS));
- if (response instanceof Throwable) {
- throw new AssertionError("ChangeShardMembersVotingStatus failed", (Throwable)response);
- }
-
- assertNull("Expected null Success response. Actual " + response, response);
-
- verifyOwner(leaderDistributedDataStore, ENTITY1, "member-4");
- verifyOwner(leaderDistributedDataStore, ENTITY2, "member-5");
- }
-
- private static void verifyGetOwnershipState(final DOMEntityOwnershipService service, final DOMEntity entity,
- final EntityOwnershipState expState) {
- Optional<EntityOwnershipState> state = service.getOwnershipState(entity);
- assertTrue("getOwnershipState present", state.isPresent());
- assertEquals("EntityOwnershipState", expState, state.get());
- }
-
- private static void verifyCandidates(final AbstractDataStore dataStore, final DOMEntity entity,
- final String... expCandidates) throws Exception {
- AssertionError lastError = null;
- Stopwatch sw = Stopwatch.createStarted();
- while (sw.elapsed(TimeUnit.MILLISECONDS) <= 10000) {
- Optional<NormalizedNode> possible = dataStore.newReadOnlyTransaction()
- .read(entityPath(entity.getType(), entity.getIdentifier()).node(Candidate.QNAME))
- .get(5, TimeUnit.SECONDS);
- try {
- assertTrue("Candidates not found for " + entity, possible.isPresent());
- Collection<String> actual = new ArrayList<>();
- for (MapEntryNode candidate: ((MapNode)possible.get()).body()) {
- actual.add(candidate.findChildByArg(CANDIDATE_NAME_NODE_ID).get().body().toString());
- }
-
- assertEquals("Candidates for " + entity, Arrays.asList(expCandidates), actual);
- return;
- } catch (AssertionError e) {
- lastError = e;
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- }
- }
-
- throw lastError;
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- private static void verifyOwner(final AbstractDataStore dataStore, final DOMEntity entity,
- final String expOwner) {
- AbstractEntityOwnershipTest.verifyOwner(expOwner, entity.getType(), entity.getIdentifier(), path -> {
- try {
- return dataStore.newReadOnlyTransaction().read(path).get(5, TimeUnit.SECONDS).get();
- } catch (Exception e) {
- return null;
- }
- });
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_ID_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_QNAME;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.candidatePath;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityEntryWithOwner;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityOwnersWithCandidate;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityOwnersWithEntityTypeEntry;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityTypeEntryWithEntityEntry;
-
-import akka.actor.ActorRef;
-import com.google.common.collect.Sets;
-import java.util.Collection;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.datastore.AbstractDataStore;
-import org.opendaylight.controller.cluster.datastore.DatastoreContext;
-import org.opendaylight.controller.cluster.datastore.DatastoreContextFactory;
-import org.opendaylight.controller.cluster.datastore.DistributedDataStore;
-import org.opendaylight.controller.cluster.datastore.Shard;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.controller.cluster.datastore.config.Configuration;
-import org.opendaylight.controller.cluster.datastore.config.ConfigurationImpl;
-import org.opendaylight.controller.cluster.datastore.config.EmptyModuleShardConfigProvider;
-import org.opendaylight.controller.cluster.datastore.utils.MockClusterWrapper;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig;
-import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
-import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import scala.concurrent.Await;
-import scala.concurrent.Future;
-import scala.concurrent.duration.FiniteDuration;
-
-/**
- * Unit tests for DistributedEntityOwnershipService.
- *
- * @author Thomas Pantelis
- */
-public class DistributedEntityOwnershipServiceTest extends AbstractClusterRefEntityOwnershipTest {
- static final String ENTITY_TYPE = "test";
- static final String ENTITY_TYPE2 = "test2";
- static final QName QNAME = QName.create("test", "2015-08-11", "foo");
- static int ID_COUNTER = 1;
-
- private final String dataStoreName = "config" + ID_COUNTER++;
- private AbstractDataStore dataStore;
-
- @Before
- public void setUp() {
- DatastoreContext datastoreContext = DatastoreContext.newBuilder().dataStoreName(dataStoreName)
- .shardInitializationTimeout(10, TimeUnit.SECONDS).build();
-
- Configuration configuration = new ConfigurationImpl(new EmptyModuleShardConfigProvider()) {
- @Override
- public Collection<MemberName> getUniqueMemberNamesForAllShards() {
- return Sets.newHashSet(MemberName.forName("member-1"));
- }
- };
-
- DatastoreContextFactory mockContextFactory = mock(DatastoreContextFactory.class);
- Mockito.doReturn(datastoreContext).when(mockContextFactory).getBaseDatastoreContext();
- Mockito.doReturn(datastoreContext).when(mockContextFactory).getShardDatastoreContext(Mockito.anyString());
-
- dataStore = new DistributedDataStore(getSystem(), new MockClusterWrapper(), configuration,
- mockContextFactory, null);
-
- dataStore.onModelContextUpdated(EOSTestUtils.SCHEMA_CONTEXT);
- }
-
- @After
- public void tearDown() {
- dataStore.close();
- }
-
- private static <T> T verifyMessage(final DistributedEntityOwnershipService mock, final Class<T> type) {
- final ArgumentCaptor<T> message = ArgumentCaptor.forClass(type);
- verify(mock).executeLocalEntityOwnershipShardOperation(message.capture());
- return message.getValue();
- }
-
- @Test
- public void testEntityOwnershipShardCreated() throws Exception {
- DistributedEntityOwnershipService service = DistributedEntityOwnershipService.start(dataStore.getActorUtils(),
- EntityOwnerSelectionStrategyConfig.newBuilder().build());
-
- Future<ActorRef> future = dataStore.getActorUtils().findLocalShardAsync(
- DistributedEntityOwnershipService.ENTITY_OWNERSHIP_SHARD_NAME);
- ActorRef shardActor = Await.result(future, FiniteDuration.create(10, TimeUnit.SECONDS));
- assertNotNull(DistributedEntityOwnershipService.ENTITY_OWNERSHIP_SHARD_NAME + " not found", shardActor);
-
- service.close();
- }
-
- @Test
- public void testRegisterCandidate() throws Exception {
- DistributedEntityOwnershipService service = spy(DistributedEntityOwnershipService.start(
- dataStore.getActorUtils(), EntityOwnerSelectionStrategyConfig.newBuilder().build()));
-
- YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
-
- DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
- verifyRegisterCandidateLocal(service, entity);
- verifyEntityOwnershipCandidateRegistration(entity, reg);
- verifyEntityCandidate(service.getLocalEntityOwnershipShard(), ENTITY_TYPE, entityId,
- dataStore.getActorUtils().getCurrentMemberName().getName());
-
- // Register the same entity - should throw exception
-
- try {
- service.registerCandidate(entity);
- fail("Expected CandidateAlreadyRegisteredException");
- } catch (CandidateAlreadyRegisteredException e) {
- // expected
- assertEquals("getEntity", entity, e.getEntity());
- }
-
- // Register a different entity - should succeed
- reset(service);
-
- DOMEntity entity2 = new DOMEntity(ENTITY_TYPE2, entityId);
- DOMEntityOwnershipCandidateRegistration reg2 = service.registerCandidate(entity2);
-
- verifyEntityOwnershipCandidateRegistration(entity2, reg2);
- verifyEntityCandidate(service.getLocalEntityOwnershipShard(), ENTITY_TYPE2, entityId,
- dataStore.getActorUtils().getCurrentMemberName().getName());
-
- service.close();
- }
-
- @Test
- public void testCloseCandidateRegistration() throws Exception {
- DistributedEntityOwnershipService service = spy(DistributedEntityOwnershipService.start(
- dataStore.getActorUtils(), EntityOwnerSelectionStrategyConfig.newBuilder().build()));
-
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, YangInstanceIdentifier.of(QNAME));
-
- DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
-
- verifyEntityOwnershipCandidateRegistration(entity, reg);
- verifyRegisterCandidateLocal(service, entity);
-
- reset(service);
- reg.close();
- UnregisterCandidateLocal unregCandidate = verifyMessage(service, UnregisterCandidateLocal.class);
- assertEquals("getEntity", entity, unregCandidate.getEntity());
-
- // Re-register - should succeed.
- reset(service);
- service.registerCandidate(entity);
- verifyRegisterCandidateLocal(service, entity);
-
- service.close();
- }
-
- @Test
- public void testListenerRegistration() {
- DistributedEntityOwnershipService service = spy(DistributedEntityOwnershipService.start(
- dataStore.getActorUtils(), EntityOwnerSelectionStrategyConfig.newBuilder().build()));
-
- YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
- DOMEntityOwnershipListener listener = mock(DOMEntityOwnershipListener.class);
-
- DOMEntityOwnershipListenerRegistration reg = service.registerListener(entity.getType(), listener);
-
- assertNotNull("EntityOwnershipListenerRegistration null", reg);
- assertEquals("getEntityType", entity.getType(), reg.getEntityType());
- assertEquals("getInstance", listener, reg.getInstance());
-
- RegisterListenerLocal regListener = verifyMessage(service, RegisterListenerLocal.class);
- assertSame("getListener", listener, regListener.getListener());
- assertEquals("getEntityType", entity.getType(), regListener.getEntityType());
-
- reset(service);
- reg.close();
- UnregisterListenerLocal unregListener = verifyMessage(service, UnregisterListenerLocal.class);
- assertEquals("getEntityType", entity.getType(), unregListener.getEntityType());
- assertSame("getListener", listener, unregListener.getListener());
-
- service.close();
- }
-
- @Test
- public void testGetOwnershipState() throws Exception {
- DistributedEntityOwnershipService service = spy(DistributedEntityOwnershipService.start(
- dataStore.getActorUtils(), EntityOwnerSelectionStrategyConfig.newBuilder().build()));
-
- final Shard mockShard = Mockito.mock(Shard.class);
- ShardDataTree shardDataTree = new ShardDataTree(mockShard, EOSTestUtils.SCHEMA_CONTEXT, TreeType.OPERATIONAL);
-
- when(service.getLocalEntityOwnershipShardDataTree()).thenReturn(shardDataTree.getDataTree());
-
- DOMEntity entity1 = new DOMEntity(ENTITY_TYPE, "one");
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, entity1.getIdentifier(), "member-1"),
- shardDataTree);
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithEntityTypeEntry(entityTypeEntryWithEntityEntry(entity1.getType(),
- entityEntryWithOwner(entity1.getIdentifier(), "member-1"))), shardDataTree);
- verifyGetOwnershipState(service, entity1, EntityOwnershipState.IS_OWNER);
-
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE,
- entity1.getIdentifier(), "member-2"), shardDataTree);
- writeNode(entityPath(entity1.getType(), entity1.getIdentifier()),
- entityEntryWithOwner(entity1.getIdentifier(), "member-2"), shardDataTree);
- verifyGetOwnershipState(service, entity1, EntityOwnershipState.OWNED_BY_OTHER);
-
- writeNode(entityPath(entity1.getType(), entity1.getIdentifier()), entityEntryWithOwner(entity1.getIdentifier(),
- ""), shardDataTree);
- verifyGetOwnershipState(service, entity1, EntityOwnershipState.NO_OWNER);
-
- DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, "two");
- Optional<EntityOwnershipState> state = service.getOwnershipState(entity2);
- assertFalse("getOwnershipState present", state.isPresent());
-
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, entity2.getIdentifier(), "member-1"),
- shardDataTree);
- writeNode(entityPath(entity2.getType(), entity2.getIdentifier()), ImmutableNodes.mapEntry(ENTITY_QNAME,
- ENTITY_ID_QNAME, entity2.getIdentifier()), shardDataTree);
- verifyGetOwnershipState(service, entity2, EntityOwnershipState.NO_OWNER);
-
- deleteNode(candidatePath(entityPath(entity2.getType(), entity2.getIdentifier()), "member-1"), shardDataTree);
- Optional<EntityOwnershipState> state2 = service.getOwnershipState(entity2);
- assertFalse("getOwnershipState present", state2.isPresent());
- service.close();
- }
-
- @Test
- public void testIsCandidateRegistered() throws CandidateAlreadyRegisteredException {
- DistributedEntityOwnershipService service = DistributedEntityOwnershipService.start(dataStore.getActorUtils(),
- EntityOwnerSelectionStrategyConfig.newBuilder().build());
-
- final DOMEntity test = new DOMEntity("test-type", "test");
-
- assertFalse(service.isCandidateRegistered(test));
-
- service.registerCandidate(test);
-
- assertTrue(service.isCandidateRegistered(test));
-
- service.close();
- }
-
- private static void verifyGetOwnershipState(final DistributedEntityOwnershipService service, final DOMEntity entity,
- final EntityOwnershipState expState) {
- Optional<EntityOwnershipState> state = service.getOwnershipState(entity);
- assertTrue("getOwnershipState present", state.isPresent());
- assertEquals("EntityOwnershipState", expState, state.get());
- }
-
- @SuppressWarnings("checkstyle:IllegalCatch")
- private void verifyEntityCandidate(final ActorRef entityOwnershipShard, final String entityType,
- final YangInstanceIdentifier entityId, final String candidateName) {
- verifyEntityCandidate(entityType, entityId, candidateName, path -> {
- try {
- return dataStore.newReadOnlyTransaction().read(path).get(5, TimeUnit.SECONDS).get();
- } catch (Exception e) {
- return null;
- }
- });
- }
-
- private static void verifyRegisterCandidateLocal(final DistributedEntityOwnershipService service,
- final DOMEntity entity) {
- RegisterCandidateLocal regCandidate = verifyMessage(service, RegisterCandidateLocal.class);
- assertEquals("getEntity", entity, regCandidate.getEntity());
- }
-
- private static void verifyEntityOwnershipCandidateRegistration(final DOMEntity entity,
- final DOMEntityOwnershipCandidateRegistration reg) {
- assertNotNull("EntityOwnershipCandidateRegistration null", reg);
- assertEquals("getInstance", entity, reg.getInstance());
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.entityownership;
-
-import java.io.File;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
-
-final class EOSTestUtils {
- static final EffectiveModelContext SCHEMA_CONTEXT = YangParserTestUtils.parseYangFiles(
- new File("src/main/yang/entity-owners.yang"));
-
- private EOSTestUtils() {
- // Hidden on purpose
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityEntryWithOwner;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityOwnersWithCandidate;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.datastore.Shard;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
-
-/**
- * Unit tests for EntityOwnerChangeListener.
- *
- * @author Thomas Pantelis
- */
-public class EntityOwnerChangeListenerTest {
- private static final String LOCAL_MEMBER_NAME = "member-1";
- private static final String REMOTE_MEMBER_NAME1 = "member-2";
- private static final String REMOTE_MEMBER_NAME2 = "member-3";
- private static final String ENTITY_TYPE = "test";
- private static final YangInstanceIdentifier ENTITY_ID1 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity1"));
- private static final YangInstanceIdentifier ENTITY_ID2 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity2"));
- private static final DOMEntity ENTITY1 = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
- private static final DOMEntity ENTITY2 = new DOMEntity(ENTITY_TYPE, ENTITY_ID2);
-
- private final Shard mockShard = Mockito.mock(Shard.class);
-
- private final ShardDataTree shardDataTree = new ShardDataTree(mockShard, EOSTestUtils.SCHEMA_CONTEXT,
- TreeType.OPERATIONAL);
- private final EntityOwnershipListenerSupport mockListenerSupport = mock(EntityOwnershipListenerSupport.class);
- private EntityOwnerChangeListener listener;
-
- @Before
- public void setup() {
- listener = new EntityOwnerChangeListener(MemberName.forName(LOCAL_MEMBER_NAME), mockListenerSupport);
- listener.init(shardDataTree);
- }
-
- @Test
- public void testOnDataTreeChanged() throws Exception {
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME));
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID2, LOCAL_MEMBER_NAME));
- verify(mockListenerSupport, never()).notifyEntityOwnershipListeners(any(DOMEntity.class), anyBoolean(),
- anyBoolean(), anyBoolean());
-
- // Write local member as owner for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, LOCAL_MEMBER_NAME));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY1, false, true, true);
-
- // Add remote member 1 as candidate for entity 1 - listener support should not get notified
-
- reset(mockListenerSupport);
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, REMOTE_MEMBER_NAME1));
- verify(mockListenerSupport, never()).notifyEntityOwnershipListeners(any(DOMEntity.class), anyBoolean(),
- anyBoolean(), anyBoolean());
-
- // Change owner to remote member 1 for entity 1
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, REMOTE_MEMBER_NAME1));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY1, true, false, true);
-
- // Change owner to remote member 2 for entity 1
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, REMOTE_MEMBER_NAME2));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY1, false, false, true);
-
- // Clear the owner for entity 1
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, ""));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY1, false, false, false);
-
- // Change owner to the local member for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, LOCAL_MEMBER_NAME));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY1, false, true, true);
-
- // Change owner to remote member 2 for entity 2
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, REMOTE_MEMBER_NAME1));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY2, false, false, true);
-
- // Change owner to the local member for entity 2
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, LOCAL_MEMBER_NAME));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY2, false, true, true);
-
- // Write local member owner for entity 2 again - expect no change
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, LOCAL_MEMBER_NAME));
- verify(mockListenerSupport, never()).notifyEntityOwnershipListeners(any(DOMEntity.class), anyBoolean(),
- anyBoolean(), anyBoolean());
-
- // Clear the owner for entity 2
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, null));
- verify(mockListenerSupport).notifyEntityOwnershipListeners(ENTITY2, true, false, false);
-
- // Clear the owner for entity 2 again - expect no change
-
- reset(mockListenerSupport);
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, null));
- verify(mockListenerSupport, never()).notifyEntityOwnershipListeners(any(DOMEntity.class), anyBoolean(),
- anyBoolean(), anyBoolean());
- }
-
- private void writeNode(final YangInstanceIdentifier path, final NormalizedNode node)
- throws DataValidationFailedException {
- AbstractEntityOwnershipTest.writeNode(path, node, shardDataTree);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import akka.actor.ActorRef;
-import akka.testkit.TestActorRef;
-import org.junit.After;
-import org.junit.Test;
-import org.opendaylight.controller.cluster.raft.TestActorFactory;
-import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-
-/**
- * Unit tests for EntityOwnershipListenerActor.
- *
- * @author Thomas Pantelis
- */
-public class EntityOwnershipListenerActorTest extends AbstractEntityOwnershipTest {
- private final TestActorFactory actorFactory = new TestActorFactory(getSystem());
-
- @After
- public void tearDown() {
- actorFactory.close();
- }
-
- @Test
- public void testOnEntityOwnershipChanged() {
- DOMEntityOwnershipListener mockListener = mock(DOMEntityOwnershipListener.class);
-
- TestActorRef<EntityOwnershipListenerActor> listenerActor = actorFactory.createTestActor(
- EntityOwnershipListenerActor.props(mockListener), actorFactory.generateActorId("listener"));
-
- DOMEntity entity = new DOMEntity("test", YangInstanceIdentifier.of(QName.create("test", "id1")));
- boolean wasOwner = false;
- boolean isOwner = true;
- boolean hasOwner = true;
- listenerActor.tell(new DOMEntityOwnershipChange(entity, EntityOwnershipChangeState.from(
- wasOwner, isOwner, hasOwner)), ActorRef.noSender());
-
- verify(mockListener, timeout(5000)).ownershipChanged(ownershipChange(entity, wasOwner, isOwner, hasOwner));
- }
-
- @Test
- public void testOnEntityOwnershipChangedWithListenerEx() {
- DOMEntityOwnershipListener mockListener = mock(DOMEntityOwnershipListener.class);
-
- DOMEntity entity1 = new DOMEntity("test", YangInstanceIdentifier.of(QName.create("test", "id1")));
- doThrow(new RuntimeException("mock")).when(mockListener).ownershipChanged(
- ownershipChange(entity1, false, true, true));
- DOMEntity entity2 = new DOMEntity("test", YangInstanceIdentifier.of(QName.create("test", "id2")));
- doNothing().when(mockListener).ownershipChanged(ownershipChange(entity2, true, false, false));
-
- TestActorRef<EntityOwnershipListenerActor> listenerActor = actorFactory.createTestActor(
- EntityOwnershipListenerActor.props(mockListener), actorFactory.generateActorId("listener"));
-
- listenerActor.tell(new DOMEntityOwnershipChange(entity1, EntityOwnershipChangeState.from(
- false, true, true)), ActorRef.noSender());
- listenerActor.tell(new DOMEntityOwnershipChange(entity2, EntityOwnershipChangeState.from(
- true, false, false)), ActorRef.noSender());
-
- verify(mockListener, timeout(5000)).ownershipChanged(ownershipChange(entity2, true, false, false));
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import akka.actor.ActorContext;
-import akka.actor.ActorRef;
-import akka.actor.Props;
-import akka.testkit.TestActorRef;
-import akka.testkit.javadsl.TestKit;
-import com.google.common.util.concurrent.Uninterruptibles;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.opendaylight.controller.cluster.raft.TestActorFactory;
-import org.opendaylight.controller.cluster.raft.utils.DoNothingActor;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import scala.collection.Iterator;
-import scala.collection.immutable.Iterable;
-
-/**
- * Unit tests for EntityOwnershipListenerSupport.
- *
- * @author Thomas Pantelis
- */
-public class EntityOwnershipListenerSupportTest extends AbstractEntityOwnershipTest {
- private final TestActorFactory actorFactory = new TestActorFactory(getSystem());
- private ActorContext actorContext;
-
- @Before
- public void setup() {
- TestActorRef<DoNothingActor> actor = actorFactory.createTestActor(
- Props.create(DoNothingActor.class), actorFactory.generateActorId("test"));
-
- actorContext = actor.underlyingActor().getContext();
- }
-
- @After
- public void tearDown() {
- actorFactory.close();
- }
-
- @Test
- public void testNotifyEntityOwnershipListeners() {
- EntityOwnershipListenerSupport support = new EntityOwnershipListenerSupport(actorContext, "test");
-
- DOMEntityOwnershipListener mockListener1 = mock(DOMEntityOwnershipListener.class, "EntityOwnershipListener1");
- DOMEntityOwnershipListener mockListener2 = mock(DOMEntityOwnershipListener.class, "EntityOwnershipListener2");
- DOMEntityOwnershipListener mockListener12 = mock(DOMEntityOwnershipListener.class,
- "EntityOwnershipListener1_2");
- String entityType1 = "type1";
- String entityType2 = "type2";
- final DOMEntity entity1 = new DOMEntity(entityType1, YangInstanceIdentifier.of(QName.create("test", "id1")));
- final DOMEntity entity2 = new DOMEntity(entityType2, YangInstanceIdentifier.of(QName.create("test", "id2")));
- final DOMEntity entity3 = new DOMEntity("noListener", YangInstanceIdentifier.of(QName.create("test", "id5")));
-
- // Add EntityOwnershipListener registrations.
-
- support.addEntityOwnershipListener(entityType1, mockListener1);
- support.addEntityOwnershipListener(entityType1, mockListener1); // register again - should be noop
- support.addEntityOwnershipListener(entityType1, mockListener12);
- support.addEntityOwnershipListener(entityType2, mockListener2);
-
- // Notify entity1 changed and verify appropriate listeners are notified.
-
- support.notifyEntityOwnershipListeners(entity1, false, true, true);
-
- verify(mockListener1, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, true, true));
- verify(mockListener12, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, true, true));
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(mockListener2, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- assertEquals("# of listener actors", 2, actorContext.children().size());
- reset(mockListener1, mockListener2, mockListener12);
-
- // Notify entity2 changed and verify appropriate listeners are notified.
-
- support.notifyEntityOwnershipListeners(entity2, false, true, true);
-
- verify(mockListener2, timeout(5000)).ownershipChanged(ownershipChange(entity2, false, true, true));
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(mockListener1, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- verify(mockListener12, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- assertEquals("# of listener actors", 3, actorContext.children().size());
- reset(mockListener1, mockListener2, mockListener12);
-
- // Notify entity3 changed and verify no listeners are notified.
-
- support.notifyEntityOwnershipListeners(entity3, true, false, true);
-
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(mockListener1, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- verify(mockListener2, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- verify(mockListener12, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- reset(mockListener1, mockListener2, mockListener12);
-
- Iterable<ActorRef> listenerActors = actorContext.children();
- assertEquals("# of listener actors", 3, listenerActors.size());
-
- // Unregister mockListener1, issue a change for entity1 and verify only remaining listeners are notified.
-
- support.removeEntityOwnershipListener(entityType1, mockListener1);
- support.notifyEntityOwnershipListeners(entity1, true, false, true);
-
- verify(mockListener12, timeout(5000)).ownershipChanged(ownershipChange(entity1, true, false, true));
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(mockListener1, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- reset(mockListener1, mockListener2, mockListener12);
-
- // Unregister all listeners and verify their listener actors are destroyed.
-
- List<TestKit> watchers = new ArrayList<>();
- for (Iterator<ActorRef> iter = listenerActors.iterator(); iter.hasNext();) {
- TestKit kit = new TestKit(getSystem());
- kit.watch(iter.next());
- watchers.add(kit);
- }
-
- support.removeEntityOwnershipListener(entityType1, mockListener12);
- support.removeEntityOwnershipListener(entityType1, mockListener12); // un-register again - should be noop
- support.removeEntityOwnershipListener(entityType2, mockListener2);
-
- Iterator<ActorRef> iter = listenerActors.iterator();
- for (TestKit kit: watchers) {
- kit.expectTerminated(kit.duration("3 seconds"), iter.next());
- }
-
- assertEquals("# of listener actors", 0, actorContext.children().size());
-
- // Re-register mockListener1 and verify it is notified.
-
- reset(mockListener1, mockListener2);
-
- support.addEntityOwnershipListener(entityType1, mockListener1);
- support.notifyEntityOwnershipListeners(entity1, false, false, true);
-
- verify(mockListener1, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, false, true));
- verify(mockListener12, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
- verify(mockListener2, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
-
- // Quickly register and unregister mockListener2 - expecting no exceptions.
-
- support.addEntityOwnershipListener(entityType1, mockListener2);
- support.removeEntityOwnershipListener(entityType1, mockListener2);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Brocade Communications 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.entityownership;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.AdditionalMatchers.or;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor.clearMessages;
-import static org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor.expectFirstMatching;
-import static org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor.expectMatching;
-
-import akka.actor.ActorRef;
-import akka.actor.PoisonPill;
-import akka.actor.Props;
-import akka.actor.Terminated;
-import akka.dispatch.Dispatchers;
-import akka.testkit.TestActorRef;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.Uninterruptibles;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Predicate;
-import org.junit.After;
-import org.junit.Test;
-import org.opendaylight.controller.cluster.access.concepts.MemberName;
-import org.opendaylight.controller.cluster.datastore.DatastoreContext;
-import org.opendaylight.controller.cluster.datastore.DatastoreContext.Builder;
-import org.opendaylight.controller.cluster.datastore.ShardTestKit;
-import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier;
-import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications;
-import org.opendaylight.controller.cluster.datastore.messages.PeerAddressResolved;
-import org.opendaylight.controller.cluster.datastore.messages.PeerDown;
-import org.opendaylight.controller.cluster.datastore.messages.PeerUp;
-import org.opendaylight.controller.cluster.datastore.messages.SuccessReply;
-import org.opendaylight.controller.cluster.entityownership.messages.CandidateAdded;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.RegisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterCandidateLocal;
-import org.opendaylight.controller.cluster.entityownership.messages.UnregisterListenerLocal;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig;
-import org.opendaylight.controller.cluster.entityownership.selectionstrategy.LastCandidateSelectionStrategy;
-import org.opendaylight.controller.cluster.raft.RaftState;
-import org.opendaylight.controller.cluster.raft.TestActorFactory;
-import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
-import org.opendaylight.controller.cluster.raft.base.messages.TimeoutNow;
-import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
-import org.opendaylight.controller.cluster.raft.messages.RequestVote;
-import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
-import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-
-/**
- * Unit tests for EntityOwnershipShard.
- *
- * @author Thomas Pantelis
- */
-public class EntityOwnershipShardTest extends AbstractEntityOwnershipTest {
- private static final String ENTITY_TYPE = "test type";
- private static final YangInstanceIdentifier ENTITY_ID1 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity1"));
- private static final YangInstanceIdentifier ENTITY_ID2 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity2"));
- private static final YangInstanceIdentifier ENTITY_ID3 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity3"));
- private static final YangInstanceIdentifier ENTITY_ID4 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity4"));
- private static final YangInstanceIdentifier ENTITY_ID5 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity5"));
- private static final String LOCAL_MEMBER_NAME = "local-member-1";
- private static final String PEER_MEMBER_1_NAME = "peer-member-1";
- private static final String PEER_MEMBER_2_NAME = "peer-member-2";
-
- private Builder dataStoreContextBuilder = DatastoreContext.newBuilder().persistent(false);
- private final TestActorFactory actorFactory = new TestActorFactory(getSystem());
-
- @After
- public void tearDown() {
- actorFactory.close();
- }
-
- @Test
- public void testOnRegisterCandidateLocal() {
- testLog.info("testOnRegisterCandidateLocal starting");
-
- ShardTestKit kit = new ShardTestKit(getSystem());
-
- TestActorRef<EntityOwnershipShard> shard = actorFactory.createTestActor(newLocalShardProps());
-
- ShardTestKit.waitUntilLeader(shard);
-
- YangInstanceIdentifier entityId = ENTITY_ID1;
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
-
- shard.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(shard, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
- verifyOwner(shard, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
-
- testLog.info("testOnRegisterCandidateLocal ending");
- }
-
- @Test
- public void testOnRegisterCandidateLocalWithNoInitialLeader() {
- testLog.info("testOnRegisterCandidateLocalWithNoInitialLeader starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(2);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId = newShardId(PEER_MEMBER_1_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId, peerMap(leaderId.toString()), PEER_MEMBER_1_NAME)), peerId.toString());
- TestEntityOwnershipShard peerShard = peer.underlyingActor();
- peerShard.startDroppingMessagesOfType(RequestVote.class);
- peerShard.startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> shard = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId.toString()), LOCAL_MEMBER_NAME), leaderId.toString());
-
- YangInstanceIdentifier entityId = ENTITY_ID1;
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
-
- shard.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Now allow RequestVotes to the peer so the shard becomes the leader. This should retry the commit.
- peerShard.stopDroppingMessagesOfType(RequestVote.class);
-
- verifyCommittedEntityCandidate(shard, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
- verifyOwner(shard, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
-
- testLog.info("testOnRegisterCandidateLocalWithNoInitialLeader ending");
- }
-
- @Test
- public void testOnRegisterCandidateLocalWithNoInitialConsensus() {
- testLog.info("testOnRegisterCandidateLocalWithNoInitialConsensus starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(2)
- .shardTransactionCommitTimeoutInSeconds(1);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId = newShardId(PEER_MEMBER_1_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId, peerMap(leaderId.toString()), PEER_MEMBER_1_NAME)), peerId.toString());
- TestEntityOwnershipShard peerShard = peer.underlyingActor();
- peerShard.startDroppingMessagesOfType(ElectionTimeout.class);
-
- // Drop AppendEntries so consensus isn't reached.
- peerShard.startDroppingMessagesOfType(AppendEntries.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId.toString()), LOCAL_MEMBER_NAME), leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- YangInstanceIdentifier entityId = ENTITY_ID1;
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
-
- leader.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Wait enough time for the commit to timeout.
- Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
-
- // Resume AppendEntries - the follower should ack the commit which should then result in the candidate
- // write being applied to the state.
- peerShard.stopDroppingMessagesOfType(AppendEntries.class);
-
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
-
- testLog.info("testOnRegisterCandidateLocalWithNoInitialConsensus ending");
- }
-
- @Test
- public void testOnRegisterCandidateLocalWithIsolatedLeader() throws Exception {
- testLog.info("testOnRegisterCandidateLocalWithIsolatedLeader starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(2)
- .shardIsolatedLeaderCheckIntervalInMillis(50);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId = newShardId(PEER_MEMBER_1_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId, peerMap(leaderId.toString()), PEER_MEMBER_1_NAME)), peerId.toString());
- TestEntityOwnershipShard peerShard = peer.underlyingActor();
- peerShard.startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId.toString()), LOCAL_MEMBER_NAME));
-
- ShardTestKit.waitUntilLeader(leader);
-
- // Drop AppendEntries and wait enough time for the shard to switch to IsolatedLeader.
- peerShard.startDroppingMessagesOfType(AppendEntries.class);
- verifyRaftState(leader, state ->
- assertEquals("getRaftState", RaftState.IsolatedLeader.toString(), state.getRaftState()));
-
- YangInstanceIdentifier entityId = ENTITY_ID1;
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
-
- leader.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Resume AppendEntries - the candidate write should now be committed.
- peerShard.stopDroppingMessagesOfType(AppendEntries.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, entityId, LOCAL_MEMBER_NAME);
-
- testLog.info("testOnRegisterCandidateLocalWithIsolatedLeader ending");
- }
-
- @Test
- public void testOnRegisterCandidateLocalWithRemoteLeader() {
- testLog.info("testOnRegisterCandidateLocalWithRemoteLeader starting");
-
- ShardTestKit kit = new ShardTestKit(getSystem());
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(2)
- .shardBatchedModificationCount(5);
-
- ShardIdentifier leaderId = newShardId(PEER_MEMBER_1_NAME);
- ShardIdentifier localId = newShardId(LOCAL_MEMBER_NAME);
- TestActorRef<TestEntityOwnershipShard> leader = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(leaderId, peerMap(localId.toString()), PEER_MEMBER_1_NAME),
- actorFactory.createActor(MessageCollectorActor.props())), leaderId.toString());
- final TestEntityOwnershipShard leaderShard = leader.underlyingActor();
-
- TestActorRef<TestEntityOwnershipShard> local = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(localId, peerMap(leaderId.toString()),LOCAL_MEMBER_NAME)), localId.toString());
- local.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- local.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID1)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
-
- // Test with initial commit timeout and subsequent retry.
-
- local.tell(dataStoreContextBuilder.shardTransactionCommitTimeoutInSeconds(1).build(), ActorRef.noSender());
- leaderShard.startDroppingMessagesOfType(BatchedModifications.class);
-
- local.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID2)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- expectFirstMatching(leaderShard.collectorActor(), BatchedModifications.class);
-
- // Send a bunch of registration messages quickly and verify.
-
- leaderShard.stopDroppingMessagesOfType(BatchedModifications.class);
- clearMessages(leaderShard.collectorActor());
-
- int max = 100;
- List<YangInstanceIdentifier> entityIds = new ArrayList<>();
- for (int i = 1; i <= max; i++) {
- YangInstanceIdentifier id = YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "test" + i));
- entityIds.add(id);
- local.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, id)), kit.getRef());
- }
-
- for (int i = 0; i < max; i++) {
- verifyCommittedEntityCandidate(local, ENTITY_TYPE, entityIds.get(i), LOCAL_MEMBER_NAME);
- }
-
- testLog.info("testOnRegisterCandidateLocalWithRemoteLeader ending");
- }
-
- @Test
- public void testOnUnregisterCandidateLocal() {
- testLog.info("testOnUnregisterCandidateLocal starting");
-
- ShardTestKit kit = new ShardTestKit(getSystem());
- TestActorRef<EntityOwnershipShard> shard = actorFactory.createTestActor(newLocalShardProps());
- ShardTestKit.waitUntilLeader(shard);
-
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
-
- // Register
-
- shard.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(shard, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(shard, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
-
- // Unregister
-
- shard.tell(new UnregisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyOwner(shard, ENTITY_TYPE, ENTITY_ID1, "");
-
- // Register again
-
- shard.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(shard, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(shard, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
-
- testLog.info("testOnUnregisterCandidateLocal ending");
- }
-
- @Test
- public void testOwnershipChanges() {
- testLog.info("testOwnershipChanges starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(2);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId1 = newShardId(PEER_MEMBER_1_NAME);
- ShardIdentifier peerId2 = newShardId(PEER_MEMBER_2_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer1 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId1, peerMap(leaderId.toString(), peerId2.toString()), PEER_MEMBER_1_NAME)),
- peerId1.toString());
- peer1.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<TestEntityOwnershipShard> peer2 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId2, peerMap(leaderId.toString(), peerId1.toString()), PEER_MEMBER_2_NAME)),
- peerId2.toString());
- peer2.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId1.toString(), peerId2.toString()), LOCAL_MEMBER_NAME),
- leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
-
- // Add a remote candidate
-
- peer1.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
-
- // Register local
-
- leader.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Verify the remote candidate becomes owner
-
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
-
- // Add another remote candidate and verify ownership doesn't change
-
- peer2.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_2_NAME);
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
-
- // Remove the second remote candidate and verify ownership doesn't change
-
- peer2.tell(new UnregisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyEntityCandidateRemoved(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_2_NAME);
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
-
- // Remove the first remote candidate and verify the local candidate becomes owner
-
- peer1.tell(new UnregisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyEntityCandidateRemoved(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
-
- // Add the second remote candidate back and verify ownership doesn't change
-
- peer2.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_2_NAME);
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
-
- // Unregister the local candidate and verify the second remote candidate becomes owner
-
- leader.tell(new UnregisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyEntityCandidateRemoved(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_2_NAME);
-
- testLog.info("testOwnershipChanges ending");
- }
-
- @Test
- public void testOwnerChangesOnPeerAvailabilityChanges() throws Exception {
- testLog.info("testOwnerChangesOnPeerAvailabilityChanges starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(4)
- .shardIsolatedLeaderCheckIntervalInMillis(100000);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId1 = newShardId(PEER_MEMBER_1_NAME);
- ShardIdentifier peerId2 = newShardId(PEER_MEMBER_2_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer1 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId1, peerMap(leaderId.toString(), peerId2.toString()), PEER_MEMBER_1_NAME)),
- peerId1.toString());
- peer1.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<TestEntityOwnershipShard> peer2 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId2, peerMap(leaderId.toString(), peerId1.toString()), PEER_MEMBER_2_NAME)),
- peerId2.toString());
- peer2.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId1.toString(), peerId2.toString()), LOCAL_MEMBER_NAME),
- leaderId.toString());
-
- verifyRaftState(leader, state ->
- assertEquals("getRaftState", RaftState.Leader.toString(), state.getRaftState()));
-
- // Send PeerDown and PeerUp with no entities
-
- leader.tell(new PeerDown(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
- leader.tell(new PeerUp(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
-
- // Add candidates for entity1 with the local leader as the owner
-
- leader.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID1)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
-
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID1)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_2_NAME);
-
- peer1.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID1)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
-
- // Add candidates for entity2 with peerMember2 as the owner
-
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID2)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
-
- peer1.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID2)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
-
- // Add candidates for entity3 with peerMember2 as the owner.
-
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID3)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
-
- leader.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID3)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
-
- peer1.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID3)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
-
- // Add only candidate peerMember2 for entity4.
-
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID4)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID4, PEER_MEMBER_2_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID4, PEER_MEMBER_2_NAME);
-
- // Add only candidate peerMember1 for entity5.
-
- peer1.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID5)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID5, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID5, PEER_MEMBER_1_NAME);
-
- // Kill peerMember2 and send PeerDown - the entities (2, 3, 4) owned by peerMember2 should get a new
- // owner selected
-
- kit.watch(peer2);
- peer2.tell(PoisonPill.getInstance(), ActorRef.noSender());
- kit.expectMsgClass(Duration.ofSeconds(5), Terminated.class);
- kit.unwatch(peer2);
-
- leader.tell(new PeerDown(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
- // Send PeerDown again - should be noop
- leader.tell(new PeerDown(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
- peer1.tell(new PeerDown(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
-
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- // no other candidates for entity4 so peerMember2 should remain owner.
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID4, PEER_MEMBER_2_NAME);
-
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID4, PEER_MEMBER_2_NAME);
-
- // Reinstate peerMember2
-
- peer2 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId2, peerMap(leaderId.toString(), peerId1.toString()), PEER_MEMBER_2_NAME)),
- peerId2.toString());
- peer2.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
- leader.tell(new PeerUp(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
- // Send PeerUp again - should be noop
- leader.tell(new PeerUp(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
- peer1.tell(new PeerUp(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
-
- // peerMember2's candidates should be removed on startup.
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_2_NAME);
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID4, PEER_MEMBER_2_NAME);
-
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID4, "");
-
- // Add back candidate peerMember2 for entities 1, 2, & 3.
-
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID1)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID2)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- peer2.tell(new RegisterCandidateLocal(new DOMEntity(ENTITY_TYPE, ENTITY_ID3)), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(peer2, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(peer2, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyCommittedEntityCandidate(peer2, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID4, "");
-
- // Kill peerMember1 and send PeerDown - entity 2 should get a new owner selected
-
- kit.watch(peer1);
- peer1.tell(PoisonPill.getInstance(), ActorRef.noSender());
- kit.expectMsgClass(Duration.ofSeconds(5), Terminated.class);
- kit.unwatch(peer1);
- leader.tell(new PeerDown(peerId1.getMemberName(), peerId1.toString()), ActorRef.noSender());
-
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
-
- // Verify the reinstated peerMember2 is fully synced.
-
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID4, "");
-
- // Reinstate peerMember1 and verify no owner changes
-
- peer1 = actorFactory.createTestActor(TestEntityOwnershipShard.props(newShardBuilder(
- peerId1, peerMap(leaderId.toString(), peerId2.toString()), PEER_MEMBER_1_NAME)), peerId1.toString());
- peer1.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
- leader.tell(new PeerUp(peerId1.getMemberName(), peerId1.toString()), ActorRef.noSender());
-
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- verifyOwner(leader, ENTITY_TYPE, ENTITY_ID4, "");
-
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_1_NAME);
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyNoEntityCandidate(leader, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_1_NAME);
-
- verifyNoEntityCandidate(peer2, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_1_NAME);
- verifyNoEntityCandidate(peer2, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_1_NAME);
- verifyNoEntityCandidate(peer2, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_1_NAME);
-
- // Verify the reinstated peerMember1 is fully synced.
-
- verifyOwner(peer1, ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME);
- verifyOwner(peer1, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyOwner(peer1, ENTITY_TYPE, ENTITY_ID3, LOCAL_MEMBER_NAME);
- verifyOwner(peer1, ENTITY_TYPE, ENTITY_ID4, "");
-
- AtomicLong leaderLastApplied = new AtomicLong();
- verifyRaftState(leader, rs -> {
- assertEquals("LastApplied up-to-date", rs.getLastApplied(), rs.getLastIndex());
- leaderLastApplied.set(rs.getLastApplied());
- });
-
- verifyRaftState(peer2, rs -> assertEquals("LastApplied", leaderLastApplied.get(), rs.getLastIndex()));
-
- // Kill the local leader and elect peer2 the leader. This should cause a new owner to be selected for
- // the entities (1 and 3) previously owned by the local leader member.
-
- peer2.tell(new PeerAddressResolved(peerId1.toString(), peer1.path().toString()), ActorRef.noSender());
- peer2.tell(new PeerUp(leaderId.getMemberName(), leaderId.toString()), ActorRef.noSender());
- peer2.tell(new PeerUp(peerId1.getMemberName(), peerId1.toString()), ActorRef.noSender());
-
- kit.watch(leader);
- leader.tell(PoisonPill.getInstance(), ActorRef.noSender());
- kit.expectMsgClass(Duration.ofSeconds(5), Terminated.class);
- kit.unwatch(leader);
- peer2.tell(new PeerDown(leaderId.getMemberName(), leaderId.toString()), ActorRef.noSender());
- peer2.tell(TimeoutNow.INSTANCE, peer2);
-
- verifyRaftState(peer2, state ->
- assertEquals("getRaftState", RaftState.Leader.toString(), state.getRaftState()));
-
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID1, PEER_MEMBER_2_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID2, PEER_MEMBER_2_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID3, PEER_MEMBER_2_NAME);
- verifyOwner(peer2, ENTITY_TYPE, ENTITY_ID4, "");
-
- testLog.info("testOwnerChangesOnPeerAvailabilityChanges ending");
- }
-
- @Test
- public void testLeaderIsolation() throws Exception {
- testLog.info("testLeaderIsolation starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId1 = newShardId(PEER_MEMBER_1_NAME);
- ShardIdentifier peerId2 = newShardId(PEER_MEMBER_2_NAME);
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(4)
- .shardIsolatedLeaderCheckIntervalInMillis(100000);
-
- TestActorRef<TestEntityOwnershipShard> peer1 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId1, peerMap(leaderId.toString(), peerId2.toString()), PEER_MEMBER_1_NAME)),
- peerId1.toString());
- peer1.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<TestEntityOwnershipShard> peer2 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId2, peerMap(leaderId.toString(), peerId1.toString()), PEER_MEMBER_2_NAME)),
- peerId2.toString());
- peer2.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- dataStoreContextBuilder = DatastoreContext.newBuilderFrom(dataStoreContextBuilder.build())
- .shardIsolatedLeaderCheckIntervalInMillis(500);
-
- TestActorRef<TestEntityOwnershipShard> leader = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(leaderId, peerMap(peerId1.toString(), peerId2.toString()), LOCAL_MEMBER_NAME)),
- leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- // Add entity1 candidates for all members with the leader as the owner
-
- DOMEntity entity1 = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
- leader.tell(new RegisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
-
- peer1.tell(new RegisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity1.getType(), entity1.getIdentifier(), PEER_MEMBER_1_NAME);
-
- peer2.tell(new RegisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity1.getType(), entity1.getIdentifier(), PEER_MEMBER_2_NAME);
-
- verifyOwner(leader, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyOwner(peer1, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyOwner(peer2, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
-
- // Add entity2 candidates for all members with peer1 as the owner
-
- DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, ENTITY_ID2);
- peer1.tell(new RegisterCandidateLocal(entity2), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
-
- peer2.tell(new RegisterCandidateLocal(entity2), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_2_NAME);
-
- leader.tell(new RegisterCandidateLocal(entity2), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity2.getType(), entity2.getIdentifier(), LOCAL_MEMBER_NAME);
-
- verifyOwner(leader, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
- verifyOwner(peer1, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
- verifyOwner(peer2, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
-
- // Add entity3 candidates for all members with peer2 as the owner
-
- DOMEntity entity3 = new DOMEntity(ENTITY_TYPE, ENTITY_ID3);
- peer2.tell(new RegisterCandidateLocal(entity3), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity3.getType(), entity3.getIdentifier(), PEER_MEMBER_2_NAME);
-
- leader.tell(new RegisterCandidateLocal(entity3), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity3.getType(), entity3.getIdentifier(), LOCAL_MEMBER_NAME);
-
- peer1.tell(new RegisterCandidateLocal(entity3), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity3.getType(), entity3.getIdentifier(), PEER_MEMBER_1_NAME);
-
- verifyOwner(leader, entity3.getType(), entity3.getIdentifier(), PEER_MEMBER_2_NAME);
- verifyOwner(peer1, entity3.getType(), entity3.getIdentifier(), PEER_MEMBER_2_NAME);
- verifyOwner(peer2, entity3.getType(), entity3.getIdentifier(), PEER_MEMBER_2_NAME);
-
- // Add listeners on all members
-
- DOMEntityOwnershipListener leaderListener = mock(DOMEntityOwnershipListener.class);
- leader.tell(new RegisterListenerLocal(leaderListener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verify(leaderListener, timeout(5000).times(3)).ownershipChanged(or(or(
- ownershipChange(entity1, false, true, true), ownershipChange(entity2, false, false, true)),
- ownershipChange(entity3, false, false, true)));
- reset(leaderListener);
-
- DOMEntityOwnershipListener peer1Listener = mock(DOMEntityOwnershipListener.class);
- peer1.tell(new RegisterListenerLocal(peer1Listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verify(peer1Listener, timeout(5000).times(3)).ownershipChanged(or(or(
- ownershipChange(entity1, false, false, true), ownershipChange(entity2, false, true, true)),
- ownershipChange(entity3, false, false, true)));
- reset(peer1Listener);
-
- DOMEntityOwnershipListener peer2Listener = mock(DOMEntityOwnershipListener.class);
- peer2.tell(new RegisterListenerLocal(peer2Listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verify(peer2Listener, timeout(5000).times(3)).ownershipChanged(or(or(
- ownershipChange(entity1, false, false, true), ownershipChange(entity2, false, false, true)),
- ownershipChange(entity3, false, true, true)));
- reset(peer2Listener);
-
- // Isolate the leader by dropping AppendEntries to the followers and incoming messages from the followers.
-
- leader.underlyingActor().startDroppingMessagesOfType(RequestVote.class);
- leader.underlyingActor().startDroppingMessagesOfType(AppendEntries.class);
-
- peer2.underlyingActor().startDroppingMessagesOfType(AppendEntries.class,
- ae -> ae.getLeaderId().equals(leaderId.toString()));
- peer1.underlyingActor().startDroppingMessagesOfType(AppendEntries.class);
-
- // Make peer1 start an election and become leader by enabling the ElectionTimeout message.
-
- peer1.underlyingActor().stopDroppingMessagesOfType(ElectionTimeout.class);
-
- // Send PeerDown to the isolated leader so it tries to re-assign ownership for the entities owned by the
- // isolated peers.
-
- leader.tell(new PeerDown(peerId1.getMemberName(), peerId1.toString()), ActorRef.noSender());
- leader.tell(new PeerDown(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
-
- verifyRaftState(leader, state ->
- assertEquals("getRaftState", RaftState.IsolatedLeader.toString(), state.getRaftState()));
-
- // Expect inJeopardy notification on the isolated leader.
-
- verify(leaderListener, timeout(5000).times(3)).ownershipChanged(or(or(
- ownershipChange(entity1, true, true, true, true), ownershipChange(entity2, false, false, true, true)),
- ownershipChange(entity3, false, false, true, true)));
- reset(leaderListener);
-
- verifyRaftState(peer1, state ->
- assertEquals("getRaftState", RaftState.Leader.toString(), state.getRaftState()));
-
- // Send PeerDown to the new leader peer1 so it re-assigns ownership for the entities owned by the
- // isolated leader.
-
- peer1.tell(new PeerDown(leaderId.getMemberName(), leaderId.toString()), ActorRef.noSender());
-
- verifyOwner(peer1, entity1.getType(), entity1.getIdentifier(), PEER_MEMBER_1_NAME);
-
- verify(peer1Listener, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, true, true));
- reset(peer1Listener);
-
- verify(peer2Listener, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, false, true));
- reset(peer2Listener);
-
- // Remove the isolation.
-
- leader.underlyingActor().stopDroppingMessagesOfType(RequestVote.class);
- leader.underlyingActor().stopDroppingMessagesOfType(AppendEntries.class);
- peer2.underlyingActor().stopDroppingMessagesOfType(AppendEntries.class);
- peer1.underlyingActor().stopDroppingMessagesOfType(AppendEntries.class);
-
- // Previous leader should switch to Follower and send inJeopardy cleared notifications for all entities.
-
- verifyRaftState(leader, state ->
- assertEquals("getRaftState", RaftState.Follower.toString(), state.getRaftState()));
-
- verify(leaderListener, timeout(5000).times(3)).ownershipChanged(or(or(
- ownershipChange(entity1, true, true, true), ownershipChange(entity2, false, false, true)),
- ownershipChange(entity3, false, false, true)));
-
- verifyOwner(leader, entity1.getType(), entity1.getIdentifier(), PEER_MEMBER_1_NAME);
- verify(leaderListener, timeout(5000)).ownershipChanged(ownershipChange(entity1, true, false, true));
-
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verifyOwner(leader, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
- verifyOwner(leader, entity3.getType(), entity3.getIdentifier(), PEER_MEMBER_2_NAME);
-
- verifyNoMoreInteractions(leaderListener);
- verifyNoMoreInteractions(peer1Listener);
- verifyNoMoreInteractions(peer2Listener);
-
- testLog.info("testLeaderIsolation ending");
- }
-
- @Test
- public void testLeaderIsolationWithPendingCandidateAdded() throws Exception {
- testLog.info("testLeaderIsolationWithPendingCandidateAdded starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId1 = newShardId(PEER_MEMBER_1_NAME);
- ShardIdentifier peerId2 = newShardId(PEER_MEMBER_2_NAME);
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(4)
- .shardIsolatedLeaderCheckIntervalInMillis(100000);
-
- TestActorRef<TestEntityOwnershipShard> peer1 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId1, peerMap(leaderId.toString(), peerId2.toString()), PEER_MEMBER_1_NAME),
- actorFactory.createActor(MessageCollectorActor.props())), peerId1.toString());
- peer1.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<TestEntityOwnershipShard> peer2 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId2, peerMap(leaderId.toString(), peerId1.toString()), PEER_MEMBER_2_NAME),
- actorFactory.createTestActor(MessageCollectorActor.props())), peerId2.toString());
- peer2.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- dataStoreContextBuilder = DatastoreContext.newBuilderFrom(dataStoreContextBuilder.build())
- .shardIsolatedLeaderCheckIntervalInMillis(500);
-
- TestActorRef<TestEntityOwnershipShard> leader = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(leaderId, peerMap(peerId1.toString(), peerId2.toString()), LOCAL_MEMBER_NAME),
- actorFactory.createTestActor(MessageCollectorActor.props())), leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- // Add listeners on all members
-
- DOMEntityOwnershipListener leaderListener = mock(DOMEntityOwnershipListener.class,
- "DOMEntityOwnershipListener-" + LOCAL_MEMBER_NAME);
- leader.tell(new RegisterListenerLocal(leaderListener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- DOMEntityOwnershipListener peer1Listener = mock(DOMEntityOwnershipListener.class,
- "DOMEntityOwnershipListener-" + PEER_MEMBER_1_NAME);
- peer1.tell(new RegisterListenerLocal(peer1Listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- DOMEntityOwnershipListener peer2Listener = mock(DOMEntityOwnershipListener.class,
- "DOMEntityOwnershipListener-" + PEER_MEMBER_2_NAME);
- peer2.tell(new RegisterListenerLocal(peer2Listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Drop the CandidateAdded message to the leader for now.
-
- leader.underlyingActor().startDroppingMessagesOfType(CandidateAdded.class);
-
- // Add an entity candidates for the leader. Since we've blocked the CandidateAdded message, it won't be
- // assigned the owner.
-
- DOMEntity entity1 = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
- leader.tell(new RegisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyCommittedEntityCandidate(peer1, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyCommittedEntityCandidate(peer2, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
-
- DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, ENTITY_ID2);
- leader.tell(new RegisterCandidateLocal(entity2), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, entity2.getType(), entity2.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyCommittedEntityCandidate(peer1, entity2.getType(), entity2.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyCommittedEntityCandidate(peer2, entity2.getType(), entity2.getIdentifier(), LOCAL_MEMBER_NAME);
-
- // Capture the CandidateAdded messages.
-
- final List<CandidateAdded> candidateAdded = expectMatching(leader.underlyingActor().collectorActor(),
- CandidateAdded.class, 2);
-
- // Drop AppendEntries to the followers containing a log entry, which will be for the owner writes after we
- // forward the CandidateAdded messages to the leader. This will leave the pending owner write tx's uncommitted.
-
- peer1.underlyingActor().startDroppingMessagesOfType(AppendEntries.class, ae -> ae.getEntries().size() > 0);
- peer2.underlyingActor().startDroppingMessagesOfType(AppendEntries.class, ae -> ae.getEntries().size() > 0);
-
- // Now forward the CandidateAdded messages to the leader and wait for it to send out the AppendEntries.
-
- leader.underlyingActor().stopDroppingMessagesOfType(CandidateAdded.class);
- leader.tell(candidateAdded.get(0), leader);
- leader.tell(candidateAdded.get(1), leader);
-
- expectMatching(peer1.underlyingActor().collectorActor(), AppendEntries.class, 2,
- ae -> ae.getEntries().size() > 0);
-
- // Verify no owner assigned.
-
- verifyNoOwnerSet(leader, entity1.getType(), entity1.getIdentifier());
- verifyNoOwnerSet(leader, entity2.getType(), entity2.getIdentifier());
-
- // Isolate the leader by dropping AppendEntries to the followers and incoming messages from the followers.
-
- leader.underlyingActor().startDroppingMessagesOfType(RequestVote.class);
- leader.underlyingActor().startDroppingMessagesOfType(AppendEntries.class);
-
- peer2.underlyingActor().startDroppingMessagesOfType(AppendEntries.class,
- ae -> ae.getLeaderId().equals(leaderId.toString()));
- peer1.underlyingActor().startDroppingMessagesOfType(AppendEntries.class);
-
- // Send PeerDown to the isolated leader - should be no-op since there's no owned entities.
-
- leader.tell(new PeerDown(peerId1.getMemberName(), peerId1.toString()), ActorRef.noSender());
- leader.tell(new PeerDown(peerId2.getMemberName(), peerId2.toString()), ActorRef.noSender());
-
- // Verify the leader transitions to IsolatedLeader.
-
- verifyRaftState(leader, state -> assertEquals("getRaftState", RaftState.IsolatedLeader.toString(),
- state.getRaftState()));
-
- // Send PeerDown to the new leader peer1.
-
- peer1.tell(new PeerDown(leaderId.getMemberName(), leaderId.toString()), ActorRef.noSender());
-
- // Make peer1 start an election and become leader by sending the TimeoutNow message.
-
- peer1.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
-
- // Verify the peer1 transitions to Leader.
-
- verifyRaftState(peer1, state -> assertEquals("getRaftState", RaftState.Leader.toString(),
- state.getRaftState()));
-
- verifyNoOwnerSet(peer1, entity1.getType(), entity1.getIdentifier());
- verifyNoOwnerSet(peer2, entity1.getType(), entity2.getIdentifier());
-
- verifyNoMoreInteractions(peer1Listener);
- verifyNoMoreInteractions(peer2Listener);
-
- // Add candidate peer1 candidate for entity2.
-
- peer1.tell(new RegisterCandidateLocal(entity2), kit.getRef());
-
- verifyOwner(peer1, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
- verify(peer1Listener, timeout(5000)).ownershipChanged(ownershipChange(entity2, false, true, true));
- verify(peer2Listener, timeout(5000)).ownershipChanged(ownershipChange(entity2, false, false, true));
-
- reset(leaderListener, peer1Listener, peer2Listener);
-
- // Remove the isolation.
-
- leader.underlyingActor().stopDroppingMessagesOfType(RequestVote.class);
- leader.underlyingActor().stopDroppingMessagesOfType(AppendEntries.class);
- peer2.underlyingActor().stopDroppingMessagesOfType(AppendEntries.class);
- peer1.underlyingActor().stopDroppingMessagesOfType(AppendEntries.class);
-
- // Previous leader should switch to Follower.
-
- verifyRaftState(leader, state -> assertEquals("getRaftState", RaftState.Follower.toString(),
- state.getRaftState()));
-
- // Send PeerUp to peer1 and peer2.
-
- peer1.tell(new PeerUp(leaderId.getMemberName(), leaderId.toString()), ActorRef.noSender());
- peer2.tell(new PeerUp(leaderId.getMemberName(), leaderId.toString()), ActorRef.noSender());
-
- // The previous leader should become the owner of entity1.
-
- verifyOwner(leader, entity1.getType(), entity1.getIdentifier(), LOCAL_MEMBER_NAME);
-
- // The previous leader's DOMEntityOwnershipListener should get 4 total notifications:
- // - inJeopardy cleared for entity1 (wasOwner=false, isOwner=false, hasOwner=false, inJeopardy=false)
- // - inJeopardy cleared for entity2 (wasOwner=false, isOwner=false, hasOwner=false, inJeopardy=false)
- // - local owner granted for entity1 (wasOwner=false, isOwner=true, hasOwner=true, inJeopardy=false)
- // - remote owner for entity2 (wasOwner=false, isOwner=false, hasOwner=true, inJeopardy=false)
- verify(leaderListener, timeout(5000).times(4)).ownershipChanged(or(
- or(ownershipChange(entity1, false, false, false), ownershipChange(entity2, false, false, false)),
- or(ownershipChange(entity1, false, true, true), ownershipChange(entity2, false, false, true))));
-
- verify(peer1Listener, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, false, true));
- verify(peer2Listener, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, false, true));
-
- // Verify entity2's owner doesn't change.
-
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verifyOwner(peer1, entity2.getType(), entity2.getIdentifier(), PEER_MEMBER_1_NAME);
-
- verifyNoMoreInteractions(leaderListener);
- verifyNoMoreInteractions(peer1Listener);
- verifyNoMoreInteractions(peer2Listener);
-
- testLog.info("testLeaderIsolationWithPendingCandidateAdded ending");
- }
-
- @Test
- public void testListenerRegistration() {
- testLog.info("testListenerRegistration starting");
-
- ShardTestKit kit = new ShardTestKit(getSystem());
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId = newShardId(PEER_MEMBER_1_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId, peerMap(leaderId.toString()), PEER_MEMBER_1_NAME)), peerId.toString());
- peer.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId.toString()), LOCAL_MEMBER_NAME), leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- String otherEntityType = "otherEntityType";
- final DOMEntity entity1 = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
- final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, ENTITY_ID2);
- final DOMEntity entity3 = new DOMEntity(ENTITY_TYPE, ENTITY_ID3);
- final DOMEntity entity4 = new DOMEntity(otherEntityType, ENTITY_ID3);
- DOMEntityOwnershipListener listener = mock(DOMEntityOwnershipListener.class);
-
- // Register listener
-
- leader.tell(new RegisterListenerLocal(listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Register a couple candidates for the desired entity type and verify listener is notified.
-
- leader.tell(new RegisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verify(listener, timeout(5000)).ownershipChanged(ownershipChange(entity1, false, true, true));
-
- leader.tell(new RegisterCandidateLocal(entity2), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verify(listener, timeout(5000)).ownershipChanged(ownershipChange(entity2, false, true, true));
- reset(listener);
-
- // Register another candidate for another entity type and verify listener is not notified.
-
- leader.tell(new RegisterCandidateLocal(entity4), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verify(listener, never()).ownershipChanged(ownershipChange(entity4));
-
- // Register remote candidate for entity1
-
- peer.tell(new RegisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
- verifyCommittedEntityCandidate(leader, ENTITY_TYPE, entity1.getIdentifier(), PEER_MEMBER_1_NAME);
-
- // Unregister the local candidate for entity1 and verify listener is notified
-
- leader.tell(new UnregisterCandidateLocal(entity1), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verify(listener, timeout(5000)).ownershipChanged(ownershipChange(entity1, true, false, true));
- reset(listener);
-
- // Unregister the listener, add a candidate for entity3 and verify listener isn't notified
-
- leader.tell(new UnregisterListenerLocal(listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- leader.tell(new RegisterCandidateLocal(entity3), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verifyOwner(leader, ENTITY_TYPE, entity3.getIdentifier(), LOCAL_MEMBER_NAME);
- Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
- verify(listener, never()).ownershipChanged(any(DOMEntityOwnershipChange.class));
-
- // Re-register the listener and verify it gets notified of currently owned entities
-
- reset(listener);
-
- leader.tell(new RegisterListenerLocal(listener, ENTITY_TYPE), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- verify(listener, timeout(5000).times(2)).ownershipChanged(or(ownershipChange(entity2, false, true, true),
- ownershipChange(entity3, false, true, true)));
- Uninterruptibles.sleepUninterruptibly(300, TimeUnit.MILLISECONDS);
- verify(listener, never()).ownershipChanged(ownershipChange(entity4));
- verify(listener, times(1)).ownershipChanged(ownershipChange(entity1));
-
- testLog.info("testListenerRegistration ending");
- }
-
- @Test
- public void testDelayedEntityOwnerSelectionWhenMaxPeerRequestsReceived() {
- testLog.info("testDelayedEntityOwnerSelectionWhenMaxPeerRequestsReceived starting");
-
- ShardTestKit kit = new ShardTestKit(getSystem());
- EntityOwnerSelectionStrategyConfig.Builder builder = EntityOwnerSelectionStrategyConfig.newBuilder()
- .addStrategy(ENTITY_TYPE, LastCandidateSelectionStrategy.class, 500);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId = newShardId(PEER_MEMBER_1_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId, peerMap(leaderId.toString()), PEER_MEMBER_1_NAME)), peerId.toString());
- peer.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId.toString()), LOCAL_MEMBER_NAME, builder.build()),
- leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
-
- // Add a remote candidate
-
- peer.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Register local
-
- leader.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Verify the local candidate becomes owner
-
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
-
- testLog.info("testDelayedEntityOwnerSelectionWhenMaxPeerRequestsReceived ending");
- }
-
- @Test
- public void testDelayedEntityOwnerSelection() {
- testLog.info("testDelayedEntityOwnerSelection starting");
-
- final ShardTestKit kit = new ShardTestKit(getSystem());
- EntityOwnerSelectionStrategyConfig.Builder builder = EntityOwnerSelectionStrategyConfig.newBuilder()
- .addStrategy(ENTITY_TYPE, LastCandidateSelectionStrategy.class, 500);
-
- dataStoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(2);
-
- ShardIdentifier leaderId = newShardId(LOCAL_MEMBER_NAME);
- ShardIdentifier peerId1 = newShardId(PEER_MEMBER_1_NAME);
- ShardIdentifier peerId2 = newShardId(PEER_MEMBER_2_NAME);
-
- TestActorRef<TestEntityOwnershipShard> peer1 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId1, peerMap(leaderId.toString(), peerId2.toString()), PEER_MEMBER_1_NAME)),
- peerId1.toString());
- peer1.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<TestEntityOwnershipShard> peer2 = actorFactory.createTestActor(TestEntityOwnershipShard.props(
- newShardBuilder(peerId2, peerMap(leaderId.toString(), peerId1.toString()), PEER_MEMBER_2_NAME)),
- peerId2.toString());
- peer2.underlyingActor().startDroppingMessagesOfType(ElectionTimeout.class);
-
- TestActorRef<EntityOwnershipShard> leader = actorFactory.createTestActor(
- newShardProps(leaderId, peerMap(peerId1.toString(), peerId2.toString()), LOCAL_MEMBER_NAME,
- builder.build()), leaderId.toString());
-
- ShardTestKit.waitUntilLeader(leader);
-
- DOMEntity entity = new DOMEntity(ENTITY_TYPE, ENTITY_ID1);
-
- // Add a remote candidate
-
- peer1.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Register local
-
- leader.tell(new RegisterCandidateLocal(entity), kit.getRef());
- kit.expectMsgClass(SuccessReply.class);
-
- // Verify the local candidate becomes owner
-
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), PEER_MEMBER_1_NAME);
- verifyCommittedEntityCandidate(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
- verifyOwner(leader, entity.getType(), entity.getIdentifier(), LOCAL_MEMBER_NAME);
-
- testLog.info("testDelayedEntityOwnerSelection ending");
- }
-
- private Props newLocalShardProps() {
- return newShardProps(newShardId(LOCAL_MEMBER_NAME), Collections.<String,String>emptyMap(), LOCAL_MEMBER_NAME);
- }
-
- private Props newShardProps(final ShardIdentifier shardId, final Map<String,String> peers,
- final String memberName) {
- return newShardProps(shardId, peers, memberName, EntityOwnerSelectionStrategyConfig.newBuilder().build());
- }
-
- private Props newShardProps(final ShardIdentifier shardId, final Map<String,String> peers, final String memberName,
- final EntityOwnerSelectionStrategyConfig config) {
- return newShardBuilder(shardId, peers, memberName).ownerSelectionStrategyConfig(config).props()
- .withDispatcher(Dispatchers.DefaultDispatcherId());
- }
-
- private EntityOwnershipShard.Builder newShardBuilder(final ShardIdentifier shardId, final Map<String, String> peers,
- final String memberName) {
- return EntityOwnershipShard.newBuilder()
- .id(shardId)
- .peerAddresses(peers)
- .datastoreContext(dataStoreContextBuilder.build())
- .schemaContextProvider(() -> EOSTestUtils.SCHEMA_CONTEXT)
- .localMemberName(MemberName.forName(memberName))
- .ownerSelectionStrategyConfig(EntityOwnerSelectionStrategyConfig.newBuilder().build());
- }
-
- private Map<String, String> peerMap(final String... peerIds) {
- ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder();
- for (String peerId: peerIds) {
- builder.put(peerId, actorFactory.createTestActorPath(peerId)).build();
- }
-
- return builder.build();
- }
-
- private static class TestEntityOwnershipShard extends EntityOwnershipShard {
- private final ActorRef collectorActor;
- private final Map<Class<?>, Predicate<?>> dropMessagesOfType = new ConcurrentHashMap<>();
-
- TestEntityOwnershipShard(final Builder builder, final ActorRef collectorActor) {
- super(builder);
- this.collectorActor = collectorActor;
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Override
- public void handleCommand(final Object message) {
- Predicate drop = dropMessagesOfType.get(message.getClass());
- if (drop == null || !drop.test(message)) {
- super.handleCommand(message);
- }
-
- if (collectorActor != null) {
- collectorActor.tell(message, ActorRef.noSender());
- }
- }
-
- void startDroppingMessagesOfType(final Class<?> msgClass) {
- dropMessagesOfType.put(msgClass, msg -> true);
- }
-
- <T> void startDroppingMessagesOfType(final Class<T> msgClass, final Predicate<T> filter) {
- dropMessagesOfType.put(msgClass, filter);
- }
-
- void stopDroppingMessagesOfType(final Class<?> msgClass) {
- dropMessagesOfType.remove(msgClass);
- }
-
- ActorRef collectorActor() {
- return collectorActor;
- }
-
- static Props props(final Builder builder) {
- return props(builder, null);
- }
-
- static Props props(final Builder builder, final ActorRef collectorActor) {
- return Props.create(TestEntityOwnershipShard.class, builder, collectorActor)
- .withDispatcher(Dispatchers.DefaultDispatcherId());
- }
- }
-}
+++ /dev/null
-/*
- * 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.entityownership;
-
-import static org.junit.Assert.assertEquals;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.ENTITY_OWNERS_PATH;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityEntryWithOwner;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityOwnersWithCandidate;
-import static org.opendaylight.controller.cluster.entityownership.EntityOwnersModel.entityPath;
-
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.opendaylight.controller.cluster.datastore.AbstractActorTest;
-import org.opendaylight.controller.cluster.datastore.Shard;
-import org.opendaylight.controller.cluster.datastore.ShardDataTree;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
-
-public class EntityOwnershipStatisticsTest extends AbstractActorTest {
- private static final String LOCAL_MEMBER_NAME = "member-1";
- private static final String REMOTE_MEMBER_NAME1 = "member-2";
- private static final String REMOTE_MEMBER_NAME2 = "member-3";
- private static final String ENTITY_TYPE = "test";
- private static final YangInstanceIdentifier ENTITY_ID1 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity1"));
- private static final YangInstanceIdentifier ENTITY_ID2 =
- YangInstanceIdentifier.of(QName.create("test", "2015-08-14", "entity2"));
-
- private final Shard mockShard = Mockito.mock(Shard.class);
-
- private final ShardDataTree shardDataTree = new ShardDataTree(mockShard, EOSTestUtils.SCHEMA_CONTEXT,
- TreeType.OPERATIONAL);
- private EntityOwnershipStatistics ownershipStatistics;
-
- @Before
- public void setup() {
- ownershipStatistics = new EntityOwnershipStatistics();
- ownershipStatistics.init(shardDataTree);
- }
-
- @Test
- public void testOnDataTreeChanged() throws Exception {
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, LOCAL_MEMBER_NAME));
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID2, LOCAL_MEMBER_NAME));
-
- // Write local member as owner for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, LOCAL_MEMBER_NAME));
- assertStatistics(ownershipStatistics.all(), LOCAL_MEMBER_NAME, 1L);
-
- // Add remote member 1 as candidate for entity 1 - ownershipStatistics support should not get notified
-
- writeNode(ENTITY_OWNERS_PATH, entityOwnersWithCandidate(ENTITY_TYPE, ENTITY_ID1, REMOTE_MEMBER_NAME1));
- assertStatistics(ownershipStatistics.all(), LOCAL_MEMBER_NAME, 1L);
-
- // Change owner to remote member 1 for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, REMOTE_MEMBER_NAME1));
- Map<String, Map<String, Long>> statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 1L);
-
- // Change owner to remote member 2 for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, REMOTE_MEMBER_NAME2));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 1L);
-
- // Clear the owner for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, ""));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- // Change owner to the local member for entity 1
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID1), entityEntryWithOwner(ENTITY_ID1, LOCAL_MEMBER_NAME));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 1L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- // Change owner to remote member 1 for entity 2
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, REMOTE_MEMBER_NAME1));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 1L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 1L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- // Change owner to the local member for entity 2
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, LOCAL_MEMBER_NAME));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 2L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- // Write local member owner for entity 2 again - expect no change
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, LOCAL_MEMBER_NAME));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 2L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- // Clear the owner for entity 2
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, ""));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 1L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- // Clear the owner for entity 2 again - expect no change
-
- writeNode(entityPath(ENTITY_TYPE, ENTITY_ID2), entityEntryWithOwner(ENTITY_ID2, ""));
- statistics = ownershipStatistics.all();
- assertStatistics(statistics, LOCAL_MEMBER_NAME, 1L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME1, 0L);
- assertStatistics(statistics, REMOTE_MEMBER_NAME2, 0L);
-
- }
-
- private static void assertStatistics(final Map<String, Map<String, Long>> statistics, final String memberName,
- final long val) {
- assertEquals(val, statistics.get(ENTITY_TYPE).get(memberName).longValue());
- }
-
- private void writeNode(final YangInstanceIdentifier path, final NormalizedNode node)
- throws DataValidationFailedException {
- AbstractEntityOwnershipTest.writeNode(path, node, shardDataTree);
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-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 java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Test;
-
-public class EntityOwnerSelectionStrategyConfigReaderTest {
-
- @Test
- public void testReadStrategies() {
- final Map<Object, Object> props = new java.util.HashMap<>();
- props.put("entity.type.test",
- "org.opendaylight.controller.cluster.entityownership.selectionstrategy.LastCandidateSelectionStrategy,100");
-
-
- final EntityOwnerSelectionStrategyConfig config = EntityOwnerSelectionStrategyConfigReader
- .loadStrategyWithConfig(props);
-
- assertTrue(config.isStrategyConfigured("test"));
-
- final 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.emptyMap());
- assertEquals(strategy, strategy1);
-
- config.clearStrategies();
-
- final EntityOwnerSelectionStrategy strategy2 = config.createStrategy("test", Collections.emptyMap());
- assertNotEquals(strategy1, strategy2);
- }
-
- @Test
- public void testReadStrategiesWithEmptyConfiguration() {
-
- final Map<Object, Object> props = new HashMap<>();
- final EntityOwnerSelectionStrategyConfig config = EntityOwnerSelectionStrategyConfigReader
- .loadStrategyWithConfig(props);
-
- assertFalse(config.isStrategyConfigured("test"));
- }
-
- @Test
- public void testReadStrategiesWithNullConfiguration() {
- final EntityOwnerSelectionStrategyConfig config = EntityOwnerSelectionStrategyConfigReader
- .loadStrategyWithConfig(null);
- assertFalse(config.isStrategyConfigured("test"));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testReadStrategiesInvalidDelay() {
- final Map<Object, Object> props = new HashMap<>();
- props.put("entity.type.test",
- "org.opendaylight.controller.cluster.entityownership.selectionstrategy.LastCandidateSelectionStrategy,foo");
- EntityOwnerSelectionStrategyConfigReader.loadStrategyWithConfig(props);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testReadStrategiesInvalidClassType() {
- final Map<Object, Object> props = new HashMap<>();
- props.put("entity.type.test", "String,100");
- EntityOwnerSelectionStrategyConfigReader.loadStrategyWithConfig(props);
- }
-
- @Test
- public void testReadStrategiesMissingDelay() {
- final Map<Object, Object> props = new HashMap<>();
- props.put("entity.type.test",
- "org.opendaylight.controller.cluster.entityownership.selectionstrategy.LastCandidateSelectionStrategy,100");
- props.put("entity.type.test1",
- "org.opendaylight.controller.cluster.entityownership.selectionstrategy.LastCandidateSelectionStrategy");
-
-
- final EntityOwnerSelectionStrategyConfig config = EntityOwnerSelectionStrategyConfigReader
- .loadStrategyWithConfig(props);
-
- assertEquals(100, config.createStrategy("test", Collections.emptyMap()).getSelectionDelayInMillis());
- assertEquals(0, config.createStrategy("test2", Collections.emptyMap()).getSelectionDelayInMillis());
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-import com.google.common.collect.Iterables;
-import java.util.Collection;
-import java.util.Map;
-
-public class LastCandidateSelectionStrategy extends AbstractEntityOwnerSelectionStrategy {
- public LastCandidateSelectionStrategy(long selectionDelayInMillis, Map<String, Long> initialStatistics) {
- super(selectionDelayInMillis, initialStatistics);
- }
-
- @Override
- public String newOwner(String currentOwner, Collection<String> viableCandidates) {
- return Iterables.getLast(viableCandidates);
- }
-}
+++ /dev/null
-/*
- * 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.entityownership.selectionstrategy;
-
-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;
-
-public class LeastLoadedCandidateSelectionStrategyTest {
-
- @Test
- public void testLeastLoadedStrategy() {
- LeastLoadedCandidateSelectionStrategy strategy = new LeastLoadedCandidateSelectionStrategy(
- 0L, Collections.<String, Long>emptyMap());
-
- 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
- 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
- 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
- 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) {
- Map<String, Long> statistics = new HashMap<>();
- for (int i = 0; i < count.length; i++) {
- statistics.put("member-" + (i + 1), count[i]);
- }
- return statistics;
- }
-
- private static Collection<String> prepareViableCandidates(int count) {
- Collection<String> viableCandidates = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- viableCandidates.add("member-" + (i + 1));
- }
- return viableCandidates;
- }
-
- private static 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)));
- }
- }
-}