From 3cc5b499ff34e221b247409530321ed02ebca03f Mon Sep 17 00:00:00 2001 From: Vaclav Demcak Date: Thu, 14 Apr 2016 11:32:29 +0200 Subject: [PATCH] Bug 5421 - Single cluster-wide service instances grouping ClusterSingletonService grouping means to run all registred service to same Group on same ClusterNode. So we could say, service goup will always run on same ClusterNode. * implement grouping functionality for ClusterSingletonService instancies * implement double candidate funct. for handling ClusterSingletonService service group instances * add junit test suite + import Mockito Change-Id: If91a6f1bf786c42c32201899bc0bb826729f0585 Signed-off-by: Vaclav Demcak --- common/artifacts/pom.xml | 5 + common/mdsal-common-api/pom.xml | 5 + .../mdsal-singleton-common-spi/pom.xml | 78 +++ ...ctClusterSingletonServiceProviderImpl.java | 18 + .../spi}/ClusterSingletonServiceGroup.java | 4 +- .../spi/ClusterSingletonServiceGroupImpl.java | 406 +++++++++++++ ...SingletonServiceRegistrationDelegator.java | 5 +- .../ClusterSingletonServiceGroupImplTest.java | 572 ++++++++++++++++++ ...letonServiceRegistrationDelegatorTest.java | 112 ++++ .../singleton/common/spi/util/TestEntity.java | 44 ++ .../spi/util/TestInstanceIdentifier.java | 49 ++ singleton-service/pom.xml | 1 + 12 files changed, 1297 insertions(+), 2 deletions(-) create mode 100644 singleton-service/mdsal-singleton-common-spi/pom.xml create mode 100644 singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/AbstractClusterSingletonServiceProviderImpl.java rename singleton-service/{mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api => mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi}/ClusterSingletonServiceGroup.java (94%) create mode 100644 singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImpl.java rename singleton-service/{mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api => mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi}/ClusterSingletonServiceRegistrationDelegator.java (87%) create mode 100644 singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImplTest.java create mode 100644 singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegatorTest.java create mode 100644 singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestEntity.java create mode 100644 singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestInstanceIdentifier.java diff --git a/common/artifacts/pom.xml b/common/artifacts/pom.xml index 8688f95cb1..f9eafaaa56 100644 --- a/common/artifacts/pom.xml +++ b/common/artifacts/pom.xml @@ -188,6 +188,11 @@ mdsal-singleton-common-api ${project.version} + + org.opendaylight.mdsal + mdsal-singleton-common-spi + ${project.version} + org.opendaylight.mdsal mdsal-singleton-dom-api diff --git a/common/mdsal-common-api/pom.xml b/common/mdsal-common-api/pom.xml index 74e3d6c4a3..e5dee0a24a 100644 --- a/common/mdsal-common-api/pom.xml +++ b/common/mdsal-common-api/pom.xml @@ -38,6 +38,11 @@ mockito-configuration test + + org.mockito + mockito-all + test + diff --git a/singleton-service/mdsal-singleton-common-spi/pom.xml b/singleton-service/mdsal-singleton-common-spi/pom.xml new file mode 100644 index 0000000000..28dc755e1c --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.opendaylight.mdsal + singleton-service + 2.1.0-SNAPSHOT + + + mdsal-singleton-common-spi + bundle + + + + org.opendaylight.mdsal + mdsal-eos-common-api + + + org.opendaylight.mdsal + mdsal-singleton-common-api + + + com.google.guava + guava + + + org.opendaylight.yangtools + concepts + + + org.opendaylight.yangtools + util + + + org.opendaylight.yangtools + yang-common + + + junit + junit + test + + + org.opendaylight.yangtools + mockito-configuration + test + + + org.mockito + mockito-all + test + + + + + scm:git:http://git.opendaylight.org/gerrit/mdsal.git + scm:git:ssh://git.opendaylight.org:29418/mdsal.git + HEAD + https://wiki.opendaylight.org/view/MD-SAL:Main + + + + ${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/ + + + + opendaylight-site + ${nexus.site.url}/${project.artifactId}/ + + + + diff --git a/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/AbstractClusterSingletonServiceProviderImpl.java b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/AbstractClusterSingletonServiceProviderImpl.java new file mode 100644 index 0000000000..02ee82966b --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/AbstractClusterSingletonServiceProviderImpl.java @@ -0,0 +1,18 @@ +/* + * 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.mdsal.singleton.common.spi; + +/** + * Abstract class {@link AbstractClusterSingletonServiceProviderImpl} represents implementations of + * {@link org.opendaylight.mdsal.singleton.common.api.CommonClusterSingletonServiceProvider}. + */ +public abstract class AbstractClusterSingletonServiceProviderImpl { + + // TODO: add implementation +} diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceGroup.java b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroup.java similarity index 94% rename from singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceGroup.java rename to singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroup.java index 9e1cdcf940..346dba63bb 100644 --- a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceGroup.java +++ b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroup.java @@ -6,12 +6,14 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.mdsal.singleton.common.api; +package org.opendaylight.mdsal.singleton.common.spi; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; import org.opendaylight.mdsal.eos.common.api.GenericEntity; import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipChange; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration; import org.opendaylight.yangtools.concepts.Path; /** diff --git a/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImpl.java b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImpl.java new file mode 100644 index 0000000000..812b9ef0b7 --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImpl.java @@ -0,0 +1,406 @@ +/* + * 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.mdsal.singleton.common.spi; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.base.Verify; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState; +import org.opendaylight.mdsal.eos.common.api.GenericEntity; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipCandidateRegistration; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipChange; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipListener; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipService; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration; +import org.opendaylight.yangtools.concepts.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link ClusterSingletonServiceGroup} + * + * @param

the instance identifier path type + * @param the GenericEntity type + * @param the GenericEntityOwnershipChange type + * @param the GenericEntityOwnershipListener type + * @param the GenericEntityOwnershipService type + */ +@VisibleForTesting +final class ClusterSingletonServiceGroupImpl

, E extends GenericEntity

, + C extends GenericEntityOwnershipChange, + G extends GenericEntityOwnershipListener, + S extends GenericEntityOwnershipService> + implements ClusterSingletonServiceGroup { + + private static final Logger LOG = LoggerFactory.getLogger(ClusterSingletonServiceGroupImpl.class.getName()); + + private final S entityOwnershipService; + private final String clusterSingletonGroupIdentifier; + private final Semaphore clusterLock = new Semaphore(1); + + /* Entity instances */ + private final E serviceEntity; + private final E doubleCandidateEntity; + + @GuardedBy("clusterLock") + private boolean hasOwnership = false; + @GuardedBy("clusterLock") + private final List serviceGroup = new LinkedList<>(); + private final ConcurrentMap> allServiceGroups; + + /* EOS Candidate Registrations */ + private GenericEntityOwnershipCandidateRegistration serviceEntityCandidateReg; + private GenericEntityOwnershipCandidateRegistration asyncCloseEntityCandidateReg; + + /** + * Class constructor + * + * @param clusterSingletonServiceGroupIdentifier not empty string as identifier + * @param mainEntity + * @param closeEntity + * @param entityOwnershipService + */ + ClusterSingletonServiceGroupImpl(final String clusterSingletonServiceGroupIdentifier, final E mainEntity, + final E closeEntity, final S entityOwnershipService, + final ConcurrentMap> allServiceGroups) { + LOG.debug("New Instance of ClusterSingletonServiceGroup {}", clusterSingletonServiceGroupIdentifier); + Preconditions.checkArgument(!Strings.isNullOrEmpty(clusterSingletonServiceGroupIdentifier)); + this.clusterSingletonGroupIdentifier = clusterSingletonServiceGroupIdentifier; + this.entityOwnershipService = Preconditions.checkNotNull(entityOwnershipService); + this.serviceEntity = Preconditions.checkNotNull(mainEntity); + this.doubleCandidateEntity = Preconditions.checkNotNull(closeEntity); + this.allServiceGroups = Preconditions.checkNotNull(allServiceGroups); + } + + @Override + public final ListenableFuture> closeClusterSingletonGroup() { + LOG.debug("Close method for service Provider {}", clusterSingletonGroupIdentifier); + boolean needReleaseLock = false; + final ListenableFuture> destroyFuture; + try { + needReleaseLock = clusterLock.tryAcquire(10, TimeUnit.SECONDS); + } catch (final Exception e) { + LOG.warn("Unexpected Exception for service Provider {} in closing phase.", clusterSingletonGroupIdentifier, e); + } finally { + if (serviceEntityCandidateReg != null) { + serviceEntityCandidateReg.close(); + serviceEntityCandidateReg = null; + } + final List> serviceCloseFutureList = new ArrayList<>(); + if (hasOwnership) { + for (final ClusterSingletonServiceRegistrationDelegator service : serviceGroup) { + serviceCloseFutureList.add(service.closeServiceInstance()); + } + } + destroyFuture = Futures.allAsList(serviceCloseFutureList); + final Semaphore finalRelease = needReleaseLock ? clusterLock : null; + Futures.addCallback(destroyFuture, newAsyncCloseCallback(finalRelease)); + } + return destroyFuture; + } + + @Override + public final void initializationClusterSingletonGroup() { + LOG.debug("Initialization ClusterSingletonGroup {}", clusterSingletonGroupIdentifier); + boolean needReleaseLock = false; + boolean needCloseProviderInstance = false; + try { + clusterLock.acquire(); + needReleaseLock = true; + Verify.verify(serviceGroup.isEmpty()); + Verify.verify(!hasOwnership); + Verify.verify(serviceEntityCandidateReg == null); + serviceEntityCandidateReg = entityOwnershipService.registerCandidate(serviceEntity); + } catch (final Exception e) { + LOG.debug("Unexpected error by registration service Provider {}", clusterSingletonGroupIdentifier, e); + needCloseProviderInstance = true; + } finally { + closeResources(needReleaseLock, needCloseProviderInstance); + } + } + + @Override + public final ClusterSingletonServiceRegistration registerService(final ClusterSingletonService service) { + LOG.debug("RegisterService method call for ClusterSingletonServiceGroup {}", clusterSingletonGroupIdentifier); + Verify.verify(clusterSingletonGroupIdentifier.equals(service.getIdentifier().getValue())); + boolean needReleaseLock = false; + boolean needCloseProviderInstance = false; + ClusterSingletonServiceRegistrationDelegator reg = null; + try { + clusterLock.acquire(); + needReleaseLock = true; + Verify.verify(serviceEntityCandidateReg != null); + reg = new ClusterSingletonServiceRegistrationDelegator(service, this); + serviceGroup.add(reg); + if (hasOwnership) { + service.instantiateServiceInstance(); + } + } catch (final Exception e) { + LOG.debug("Unexpected error by registration service Provider {}", clusterSingletonGroupIdentifier, e); + needCloseProviderInstance = true; + } finally { + closeResources(needReleaseLock, needCloseProviderInstance); + } + return reg; + } + + @Override + public final void unregisterService(final ClusterSingletonService service) { + LOG.debug("UnregisterService method call for ClusterSingletonServiceGroup {}", clusterSingletonGroupIdentifier); + Verify.verify(clusterSingletonGroupIdentifier.equals(service.getIdentifier().getValue())); + boolean needReleaseLock = false; + boolean needCloseProviderInstance = false; + try { + clusterLock.acquire(); + needReleaseLock = true; + serviceGroup.remove(service); + if (hasOwnership) { + service.closeServiceInstance(); + } + } catch (final Exception e) { + LOG.debug("Unexpected error by registration service Provider {}", clusterSingletonGroupIdentifier, e); + needCloseProviderInstance = true; + } finally { + closeResources(needReleaseLock, needCloseProviderInstance); + if (serviceGroup.isEmpty()) { + this.closeClusterSingletonGroup(); + } + } + } + + @Override + public final void ownershipChanged(final C ownershipChange) { + LOG.debug("Ownership change {} for ClusterSingletonServiceGrou {}", ownershipChange, + clusterSingletonGroupIdentifier); + try { + if (ownershipChange.inJeopardy()) { + LOG.warn("Cluster Node lost connection to another cluster nodes {}", ownershipChange); + lostOwnership(); + return; + } + if (serviceEntity.equals(ownershipChange.getEntity())) { + if (EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED.equals(ownershipChange.getState())) { + /* + * SLAVE to MASTER : ownershipChange.getState().isOwner() && !ownershipChange.getState().wasOwner() + */ + tryToTakeOwnership(); + } else if (EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NEW_OWNER.equals(ownershipChange.getState()) + || EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER + .equals(ownershipChange.getState())) { + /* + * MASTER to SLAVE : !ownershipChange.getState().isOwner() && ownershipChange.getState().wasOwner() + */ + lostOwnership(); + } else { + /* Not needed notifications */ + LOG.debug("Not processed entity OwnershipChange {} in service Provider {}", ownershipChange, + clusterSingletonGroupIdentifier); + } + } else if (doubleCandidateEntity.equals(ownershipChange.getEntity())) { + if (EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED.equals(ownershipChange.getState())) { + /* + * SLAVE to MASTER : ownershipChange.getState().isOwner() && !ownershipChange.getState().wasOwner() + */ + takeOwnership(); + } else if (EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NEW_OWNER.equals(ownershipChange.getState()) + || EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER + .equals(ownershipChange.getState())) { + /* + * MASTER to SLAVE : !ownershipChange.getState().isOwner() && ownershipChange.getState().wasOwner() + */ + LOG.warn("Unexpected lost doubleCandidate {} leadership. Close service instance {}", + doubleCandidateEntity, clusterSingletonGroupIdentifier); + lostOwnership(); + } else { + /* Not needed notifications */ + LOG.debug("Not processed doubleCandidate OwnershipChange {} in service Provider {}", + ownershipChange, clusterSingletonGroupIdentifier); + } + } else { + LOG.warn("Unexpected EntityOwnershipChangeEvent for entity {}", ownershipChange); + } + } catch (final Exception e) { + LOG.error("Unexpected Exception for service Provider {}", clusterSingletonGroupIdentifier, e); + // TODO : think about close ... is it necessary? + } + } + + /* + * Help method to registerated DoubleCandidateEntity. It is first step + * before the actual instance take Leadership. + */ + private void tryToTakeOwnership() { + LOG.debug("TryToTakeLeadership method for service Provider {}", clusterSingletonGroupIdentifier); + boolean needReleaseLock = false; + boolean needCloseProviderInstance = false; + try { + clusterLock.acquire(); + needReleaseLock = true; + Verify.verify(serviceEntityCandidateReg != null); + Verify.verify(asyncCloseEntityCandidateReg == null); + asyncCloseEntityCandidateReg = entityOwnershipService.registerCandidate(doubleCandidateEntity); + } catch (final Exception e) { + LOG.error("Unexpected exception state for service Provider {} in TryToTakeLeadership", + clusterSingletonGroupIdentifier, e); + needCloseProviderInstance = true; + } finally { + closeResources(needReleaseLock, needCloseProviderInstance); + } + } + + /* + * Help method calls setupService method for create single cluster-wide service instance. + */ + private void takeOwnership() { + LOG.debug("TakeLeadership method for service Provider {}", clusterSingletonGroupIdentifier); + boolean needReleaseLock = false; + boolean needCloseProviderInstance = false; + try { + clusterLock.acquire(); + needReleaseLock = true; + Verify.verify(serviceEntityCandidateReg != null); + Verify.verify(asyncCloseEntityCandidateReg != null); + for (final ClusterSingletonServiceRegistrationDelegator service : serviceGroup) { + service.instantiateServiceInstance(); + } + hasOwnership = true; + } catch (final Exception e) { + LOG.error("Unexpected exception state for service Provider {} in TakeLeadership", + clusterSingletonGroupIdentifier, e); + needCloseProviderInstance = true; + } finally { + closeResources(needReleaseLock, needCloseProviderInstance); + } + } + + /* + * Help method calls suspendService method for stop this single cluster-wide service instance. + * The last async. step has to close DoubleCandidateRegistration reference what should initialize + * new election for DoubleCandidateEntity. + */ + private void lostOwnership() { + LOG.debug("LostLeadership method for service Provider {}", clusterSingletonGroupIdentifier); + boolean needReleaseLock = false; + boolean needCloseProviderInstance = false; + try { + clusterLock.acquire(); + needReleaseLock = true; + Verify.verify(serviceEntityCandidateReg != null); + final List> serviceCloseFutureList = new ArrayList<>(); + if (hasOwnership) { + Verify.verify(asyncCloseEntityCandidateReg != null); + for (final ClusterSingletonServiceRegistrationDelegator service : serviceGroup) { + serviceCloseFutureList.add(service.closeServiceInstance()); + } + } + + final ListenableFuture> destroyFuture = Futures.allAsList(serviceCloseFutureList); + Futures.addCallback(destroyFuture, newAsyncCloseCallback(clusterLock)); + /* + * We wish to stop all possible EOS activitis before we don't close + * a close candidate registration that acts as a guard. So we don't want + * to release Semaphore (clusterLock) before we are not fully finished. + * Semaphore lock release has to be realized as FutureCallback after a service + * instance has fully closed prior to relinquishing service ownership. + */ + needReleaseLock = false; + } catch (final Exception e) { + LOG.error("Unexpected exception state for service Provider {} in LostLeadership", + clusterSingletonGroupIdentifier, e); + needCloseProviderInstance = true; + } finally { + closeResources(needReleaseLock, needCloseProviderInstance); + } + } + + /* + * Help method for finalization every acquired functionality + */ + private void closeResources(final boolean needReleaseLock, final boolean needCloseProvider) { + if (needReleaseLock) { + clusterLock.release(); + } + if (needCloseProvider) { + final ListenableFuture> closeFutureList = this.closeClusterSingletonGroup(); + Futures.addCallback(closeFutureList, new FutureCallback>() { + + @Override + public void onSuccess(final List result) { + removeThisGroupFromProvider(null); + } + + @Override + public void onFailure(final Throwable t) { + removeThisGroupFromProvider(t); + } + }); + } + } + + /* + * Help method for final ClusterSingletonGroup removing + */ + protected final void removeThisGroupFromProvider(@Nullable final Throwable t) { + LOG.debug("Removing ClusterSingletonServiceGroup {}", clusterSingletonGroupIdentifier); + if (t != null) { + LOG.warn("Unexpected problem by closingResources ClusterSingletonServiceGroup {}", + clusterSingletonGroupIdentifier); + } + allServiceGroups.remove(clusterSingletonGroupIdentifier, this); + } + + /* + * Help method creates FutureCallback for suspend Future + */ + private FutureCallback> newAsyncCloseCallback(@Nullable final Semaphore semaphore) { + final Consumer closeEntityCandidateRegistration = (@Nullable final Throwable t) -> { + if (t != null) { + LOG.warn("Unexpected error closing service instance {}", clusterSingletonGroupIdentifier, t); + } else { + LOG.debug("Destroy service Instance {} is success", clusterSingletonGroupIdentifier); + } + if (asyncCloseEntityCandidateReg != null) { + asyncCloseEntityCandidateReg.close(); + asyncCloseEntityCandidateReg = null; + } + if (semaphore != null) { + semaphore.release(); + } + allServiceGroups.remove(clusterSingletonGroupIdentifier, this); + }; + + return new FutureCallback>() { + + @Override + public void onSuccess(final List result) { + closeEntityCandidateRegistration.accept(null); + } + + @Override + public void onFailure(final Throwable t) { + closeEntityCandidateRegistration.accept(t); + } + }; + } + +} diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistrationDelegator.java b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegator.java similarity index 87% rename from singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistrationDelegator.java rename to singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegator.java index e062a4ecd1..fb8fb60195 100644 --- a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistrationDelegator.java +++ b/singleton-service/mdsal-singleton-common-spi/src/main/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegator.java @@ -6,10 +6,13 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.mdsal.singleton.common.api; +package org.opendaylight.mdsal.singleton.common.spi; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.ListenableFuture; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration; +import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier; /** * Package protected help class represent a Delegator for {@link ClusterSingletonService} diff --git a/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImplTest.java b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImplTest.java new file mode 100644 index 0000000000..13b09c9685 --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceGroupImplTest.java @@ -0,0 +1,572 @@ +/* + * 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.mdsal.singleton.common.spi; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import com.google.common.util.concurrent.Futures; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipCandidateRegistration; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipChange; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipListener; +import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipService; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration; +import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier; +import org.opendaylight.mdsal.singleton.common.spi.util.TestEntity; +import org.opendaylight.mdsal.singleton.common.spi.util.TestInstanceIdentifier; + +/** + * Testing {@link ClusterSingletonServiceGroupImpl} + */ +public class ClusterSingletonServiceGroupImplTest { + + private static final String SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.ServiceEntityType"; + private static final String CLOSE_SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.AsyncServiceCloseEntityType"; + private static final String SERVICE_IDENTIFIER = "TestServiceIdent"; + private static final ServiceGroupIdentifier SERVICE_GROUP_IDENT = ServiceGroupIdentifier.create(SERVICE_IDENTIFIER); + + @Mock + private ClusterSingletonService mockClusterSingletonService; + @Mock + private ClusterSingletonService mockClusterSingletonServiceSecond; + @Mock + private GenericEntityOwnershipCandidateRegistration mockEntityCandReg; + @Mock + private GenericEntityOwnershipCandidateRegistration mockCloseEntityCandReg; + @Mock + private GenericEntityOwnershipListener> mockEosListener; + + @Mock + private GenericEntityOwnershipService>> mockEosService; + + private ClusterSingletonServiceGroupImpl, + GenericEntityOwnershipListener>, + GenericEntityOwnershipService>>> singletonServiceGroup; + + private final TestEntity mainEntity = new TestEntity(SERVICE_ENTITY_TYPE, SERVICE_IDENTIFIER); + private final TestEntity closeEntity = new TestEntity(CLOSE_SERVICE_ENTITY_TYPE, SERVICE_IDENTIFIER); + private final ConcurrentMap> map = new ConcurrentHashMap<>(); + + /** + * Initialization functionality for every Tests in this suite + * + * @throws Exception - unexpected exception + */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + doReturn(mockEntityCandReg).when(mockEosService).registerCandidate(mainEntity); + doReturn(mockCloseEntityCandReg).when(mockEosService).registerCandidate(closeEntity); + doNothing().when(mockEntityCandReg).close(); + doNothing().when(mockCloseEntityCandReg).close(); + doNothing().when(mockClusterSingletonService).instantiateServiceInstance(); + doReturn(Futures.immediateFuture(null)).when(mockClusterSingletonService).closeServiceInstance(); + + doReturn(SERVICE_GROUP_IDENT).when(mockClusterSingletonService).getIdentifier(); + doReturn(SERVICE_GROUP_IDENT).when(mockClusterSingletonServiceSecond).getIdentifier(); + + singletonServiceGroup = new ClusterSingletonServiceGroupImpl(SERVICE_IDENTIFIER, mainEntity, closeEntity, mockEosService, map); + } + + /** + * Test NULL ServiceIdent input for new ServiceGroup instance + * + * @throws Exception - unexpected exception + */ + @Test(expected = IllegalArgumentException.class) + public void instantiationClusterSingletonServiceGroupNullIdentTest() throws Exception { + singletonServiceGroup = new ClusterSingletonServiceGroupImpl(null, mainEntity, closeEntity, mockEosService, map); + } + + /** + * Test empty ServiceIdent input for new ServiceGroup instance + * + * @throws Exception - unexpected exception + */ + @Test(expected = IllegalArgumentException.class) + public void instantiationClusterSingletonServiceGroupEmptyIdentTest() throws Exception { + singletonServiceGroup = new ClusterSingletonServiceGroupImpl("", mainEntity, closeEntity, mockEosService, map); + } + + /** + * Test NULL MainEntity input for new ServiceGroup instance + * + * @throws Exception - unexpected exception + */ + @Test(expected = NullPointerException.class) + public void instantiationClusterSingletonServiceGroupNullMainEntityTest() throws Exception { + singletonServiceGroup = new ClusterSingletonServiceGroupImpl(SERVICE_IDENTIFIER, null, closeEntity, mockEosService, map); + } + + /** + * Test NULL MainEntity input for new ServiceGroup instance + * + * @throws Exception - unexpected exception + */ + @Test(expected = NullPointerException.class) + public void instantiationClusterSingletonServiceGroupNullCloseEntityTest() throws Exception { + singletonServiceGroup = new ClusterSingletonServiceGroupImpl(SERVICE_IDENTIFIER, mainEntity, null, mockEosService, map); + } + + /** + * Test NULL MainEntity input for new ServiceGroup instance + * + * @throws Exception - unexpected exception + */ + @Test(expected = NullPointerException.class) + public void instantiationClusterSingletonServiceGroupNullEOS_Test() throws Exception { + singletonServiceGroup = new ClusterSingletonServiceGroupImpl(SERVICE_IDENTIFIER, mainEntity, closeEntity, null, map); + } + + /** + * Test NULL MainEntity input for new ServiceGroup instance + * + * @throws Exception - unexpected exception + */ + @Test(expected = NullPointerException.class) + public void instantiationClusterSingletonServiceGroupNullMapRefTest() throws Exception { + singletonServiceGroup = new ClusterSingletonServiceGroupImpl(SERVICE_IDENTIFIER, mainEntity, closeEntity, mockEosService, null); + } + + /** + * Test GoldPath for initialization ServiceGroup + * + * @throws Exception - unexpected exception + */ + @Test + public void initializationClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + } + + /** + * Test GoldPath for NO-TO-SLAVE entity Candidate role change + * + * @throws Exception - unexpected exception + */ + @Test + public void initializationSlaveTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToSlave()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService, never()).registerCandidate(closeEntity); + } + + /** + * Test GoldPath for NO-TO-SLAVE but without MASTER entity Candidate role change + * + * @throws Exception - unexpected exception + */ + @Test + public void initializationNoMasterTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToSlaveNoMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService, never()).registerCandidate(closeEntity); + } + + /** + * Test GoldPath for InJeopardy entity Candidate role change + * + * @throws Exception - unexpected exception + */ + @Test + public void initializationInJeopardyTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToJeopardy()); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService, never()).registerCandidate(closeEntity); + } + + /** + * Test GoldPath for registration SingletonService + * + * @throws Exception - unexpected exception + */ + @Test + public void serviceRegistrationClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + } + + /** + * Test GoldPath for registration SingletonService + * + * @throws Exception - unexpected exception + */ + @Test + public void serviceRegistrationClusterSingletonServiceGroupTwoServiceTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + final ClusterSingletonServiceRegistration reg2 = singletonServiceGroup + .registerService(mockClusterSingletonServiceSecond); + Assert.assertNotNull(reg2); + } + + /** + * Test GoldPath for unregistration SingletonService don't call closeServiceInstance + * without mastership and don't remove ServiceGroup from map + * + * @throws Exception - unexpected exception + */ + @Test + public void serviceUnregistrationClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + reg.close(); + verify(mockClusterSingletonService, never()).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + /** + * Test GoldPath for unregistration SingletonService don't call closeServiceInstance + * without mastership and don't remove ServiceGroup from map + * + * @throws Exception - unexpected exception + */ + @Test + public void serviceUnregistrationClusterSingletonServiceGroupTwoServicesTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + final ClusterSingletonServiceRegistration reg2 = singletonServiceGroup + .registerService(mockClusterSingletonServiceSecond); + Assert.assertNotNull(reg2); + reg.close(); + verify(mockClusterSingletonService, never()).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNotNull(serviceGroup); + } + + /** + * Test GoldPath get Slave role for registered main entity + * + * @throws Exception - unexpected exception + */ + @Test + public void getSlaveClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToSlave()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + } + + /** + * Test GoldPath get Master role for registered main entity + * + * @throws Exception - unexpected exception + */ + @Test + public void tryToTakeLeaderClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + } + + /** + * Test GoldPath get Master role for registered close entity + * + * @throws Exception - unexpected exception + */ + @Test + public void takeMasterClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getDoubleEntityToMaster()); + verify(mockClusterSingletonService).instantiateServiceInstance(); + } + + /** + * Test GoldPath get Master role for registered entity but initial Slave + * role for closeEntity + * + * @throws Exception - unexpected exception + */ + @Test + public void waitToTakeMasterClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getInitDoubleEntityToSlave()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockClusterSingletonService, never()).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNotNull(serviceGroup); + } + + /** + * Test inJeopardy validation during wait phase for Master role for closeEntity + * + * @throws Exception - unexpected exception + */ + @Test + public void inJeopardyInWaitPhaseClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getEntityToJeopardy()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockClusterSingletonService, never()).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + /** + * Test inJeopardy validation during wait phase for Master role for closeEntity + * + * @throws Exception - unexpected exception + */ + @Test + public void inJeopardyInWaitPhaseClusterSingletonServiceGroupTwoServiceTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + final ClusterSingletonServiceRegistration reg2 = singletonServiceGroup + .registerService(mockClusterSingletonServiceSecond); + Assert.assertNotNull(reg2); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getEntityToJeopardy()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockClusterSingletonService, never()).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + /** + * Test inJeopardy validation for holding leadership + * + * @throws Exception - unexpected exception + */ + @Test + public void inJeopardyLeaderClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getDoubleEntityToMaster()); + verify(mockClusterSingletonService).instantiateServiceInstance(); + singletonServiceGroup.ownershipChanged(getEntityToJeopardy()); + verify(mockClusterSingletonService).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + /** + * Test GoldPath for SLAVE-TO-MASTER entity Candidate role change + * + * @throws Exception - unexpected exception + */ + @Test + public void lostLeaderClusterSingletonServiceGroupTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getDoubleEntityToMaster()); + verify(mockClusterSingletonService).instantiateServiceInstance(); + singletonServiceGroup.ownershipChanged(getEntityToSlave()); + verify(mockClusterSingletonService).closeServiceInstance(); + } + + /** + * Test checks validation Error processing for SLAVE-TO-MASTER entity Candidate role change. + * Not initialized provider has to close and remove all singletonServices from Group and + * Group itself remove too. + * + * @throws Exception - unexpected exception + */ + @Test + public void tryToTakeLeaderForNotInitializedGroupTest() throws Exception { + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNull(reg); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + /** + * Test checks closing procesing for close {@link ClusterSingletonServiceRegistration} + * + * @throws Exception - unexpected exception + */ + @Test + public void checkClosingRegistrationTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getDoubleEntityToMaster()); + verify(mockClusterSingletonService).instantiateServiceInstance(); + reg.close(); + verify(mockClusterSingletonService).closeServiceInstance(); + } + + /** + * Test checks validation Error processing for MASTER-TO-SLAVE closeEntity Candidate role change + * + * @throws Exception - unexpected exception + */ + @Test + public void checkClosingUnexpectedDoubleEntityForMasterOwnershipChangeRegistrationTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToMaster()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getDoubleEntityToMaster()); + verify(mockClusterSingletonService).instantiateServiceInstance(); + singletonServiceGroup.ownershipChanged(getDoubleEntityToSlave()); + verify(mockClusterSingletonService).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + /** + * Test checks validation Error processing for MASTER-TO-SLAVE closeEntity Candidate role change + * without closeEntity registration + * + * @throws Exception - unexpected exception + */ + @Test + public void checkClosingUnexpectedDoubleEntityForSlaveOwnershipChangeRegistrationTest() throws Exception { + singletonServiceGroup.initializationClusterSingletonGroup(); + map.putIfAbsent(SERVICE_IDENTIFIER, singletonServiceGroup); + verify(mockEosService).registerCandidate(mainEntity); + final ClusterSingletonServiceRegistration reg = singletonServiceGroup + .registerService(mockClusterSingletonService); + Assert.assertNotNull(reg); + singletonServiceGroup.ownershipChanged(getEntityToSlave()); + verify(mockClusterSingletonService, never()).instantiateServiceInstance(); + verify(mockEosService, never()).registerCandidate(closeEntity); + singletonServiceGroup.ownershipChanged(getDoubleEntityToSlave()); + verify(mockClusterSingletonService, never()).closeServiceInstance(); + final ClusterSingletonServiceGroup serviceGroup = map.get(SERVICE_IDENTIFIER); + Assert.assertNull(serviceGroup); + } + + private GenericEntityOwnershipChange getEntityToMaster() { + return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(false, true, true)); + } + + private GenericEntityOwnershipChange getEntityToSlave() { + return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(true, false, true)); + } + + private GenericEntityOwnershipChange getEntityToSlaveNoMaster() { + return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(true, false, false)); + } + + private GenericEntityOwnershipChange getDoubleEntityToMaster() { + return new GenericEntityOwnershipChange<>(closeEntity, EntityOwnershipChangeState.from(false, true, true)); + } + + private GenericEntityOwnershipChange getDoubleEntityToSlave() { + return new GenericEntityOwnershipChange<>(closeEntity, EntityOwnershipChangeState.from(true, false, true)); + } + + private GenericEntityOwnershipChange getInitDoubleEntityToSlave() { + return new GenericEntityOwnershipChange<>(closeEntity, EntityOwnershipChangeState.from(false, false, true)); + } + + private GenericEntityOwnershipChange getEntityToJeopardy() { + return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(false, false, false), true); + } + +} diff --git a/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegatorTest.java b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegatorTest.java new file mode 100644 index 0000000000..fed77857ab --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/ClusterSingletonServiceRegistrationDelegatorTest.java @@ -0,0 +1,112 @@ +/* + * 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.mdsal.singleton.common.spi; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import com.google.common.util.concurrent.Futures; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService; +import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier; + +/** + * Testing {@link ClusterSingletonServiceRegistrationDelegator} + */ +public class ClusterSingletonServiceRegistrationDelegatorTest { + + private static final String SERVICE_IDENTIFIER_NAME = "TestServiceIdent"; + private static final ServiceGroupIdentifier SERVICE_IDENTIFIER = ServiceGroupIdentifier + .create(SERVICE_IDENTIFIER_NAME); + + @Mock + private ClusterSingletonServiceGroup mockClusterSingletonServiceGroup; + @Mock + private ClusterSingletonService mockClusterSingletonService; + + private ClusterSingletonServiceRegistrationDelegator delegator; + + /** + * Initialization functionality for every Tests in this suite + * + * @throws Exception - unexpected setup exception + */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + doNothing().when(mockClusterSingletonServiceGroup) + .unregisterService(any(ClusterSingletonServiceRegistrationDelegator.class)); + doReturn(SERVICE_IDENTIFIER).when(mockClusterSingletonService).getIdentifier(); + doNothing().when(mockClusterSingletonService).instantiateServiceInstance(); + doReturn(Futures.immediateFuture(null)).when(mockClusterSingletonService).closeServiceInstance(); + delegator = new ClusterSingletonServiceRegistrationDelegator(mockClusterSingletonService, + mockClusterSingletonServiceGroup); + } + + /** + * Test create input with {@link ClusterSingletonService} as null + */ + @Test(expected = NullPointerException.class) + public void testSetupNullService() { + delegator = new ClusterSingletonServiceRegistrationDelegator(null, mockClusterSingletonServiceGroup); + } + + /** + * Test create input with {@link ClusterSingletonServiceGroupImpl} as null + */ + @Test(expected = NullPointerException.class) + public void testSetupNullGroup() { + delegator = new ClusterSingletonServiceRegistrationDelegator(mockClusterSingletonService, null); + } + + /** + * Test a method delegation {@link ClusterSingletonService#instantiateServiceInstance()} + */ + @Test + public void testInstatiateServiceDelegMethod() { + delegator.instantiateServiceInstance(); + verify(mockClusterSingletonService).instantiateServiceInstance(); + } + + /** + * Test a method delegation {@link ClusterSingletonService#instantiateServiceInstance()} + */ + @Test + public void testCloseServiceDelegMethod() { + delegator.closeServiceInstance(); + verify(mockClusterSingletonService).closeServiceInstance(); + } + + /** + * Test a method delegation {@link ClusterSingletonService#getIdentifier()} + */ + @Test + public void testGetServiceIdentifierDelegMethod() { + final String serviceIdentifier = delegator.getServiceGroupIdentifier(); + Assert.assertEquals(SERVICE_IDENTIFIER_NAME, serviceIdentifier); + verify(mockClusterSingletonService).getIdentifier(); + } + + /** + * Test a close method delegation to {@link ClusterSingletonServiceGroupImpl#unregisterService(ClusterSingletonService)} + * + * @throws Exception is from AutoclosableInterface + */ + @Test + public void testCloseMethod() throws Exception { + delegator.close(); + verify(mockClusterSingletonServiceGroup).unregisterService(delegator); + } +} diff --git a/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestEntity.java b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestEntity.java new file mode 100644 index 0000000000..d2d55d7136 --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestEntity.java @@ -0,0 +1,44 @@ +/* + * 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.mdsal.singleton.common.spi.util; + +import javax.annotation.Nonnull; +import org.opendaylight.mdsal.eos.common.api.GenericEntity; +import org.opendaylight.yangtools.yang.common.QName; + +/** + * Test util class. + */ +public class TestEntity extends GenericEntity { + private static final long serialVersionUID = 1L; + + static final QName ENTITY = QName + .create("urn:opendaylight:params:xml:ns:yang:mdsal:core:test-entity", "2016-06-06", "entity").intern(); + static final QName ENTITY_NAME = QName.create(ENTITY, "name").intern(); + + /** Constructs an instance. + * + * @param type the entity type + * @param id the entity id. + */ + public TestEntity(@Nonnull final String type, @Nonnull final TestInstanceIdentifier id) { + super(type, id); + } + + /** + * Construct an Entity with an with a name. The general-entity schema is used to construct the + * {@link TestInstanceIdentifier}. + * + * @param type the type of the entity + * @param entityName the name of the entity used to construct a general-entity YangInstanceIdentifier + */ + public TestEntity(@Nonnull final String type, @Nonnull final String entityName) { + super(type, new TestInstanceIdentifier(entityName)); + } +} diff --git a/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestInstanceIdentifier.java b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestInstanceIdentifier.java new file mode 100644 index 0000000000..feb3227bfb --- /dev/null +++ b/singleton-service/mdsal-singleton-common-spi/src/test/java/org/opendaylight/mdsal/singleton/common/spi/util/TestInstanceIdentifier.java @@ -0,0 +1,49 @@ +/* + * 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.mdsal.singleton.common.spi.util; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import java.util.LinkedList; +import java.util.List; +import org.opendaylight.yangtools.concepts.Path; + +/** + * Test helper class. {@link Path} for testing only + */ +public class TestInstanceIdentifier implements Path { + + static final TestInstanceIdentifier EMPTY_INSTANCE = new TestInstanceIdentifier(ImmutableList.of()); + + private final ImmutableList path; + + /** + * {@link TestInstanceIdentifier} constructor + * + * @param path - path + */ + public TestInstanceIdentifier(final Iterable path) { + Preconditions.checkArgument(path != null); + final List tiis = new LinkedList<>(); + for (final TestInstanceIdentifier t : path) { + tiis.add(t.toString()); + } + this.path = ImmutableList.copyOf(tiis); + } + + TestInstanceIdentifier(final String path) { + this.path = ImmutableList.of(path); + } + + @Override + public boolean contains(final TestInstanceIdentifier other) { + return path.contains(other); + } + +} diff --git a/singleton-service/pom.xml b/singleton-service/pom.xml index 65137d8cc5..b965b7125f 100644 --- a/singleton-service/pom.xml +++ b/singleton-service/pom.xml @@ -27,6 +27,7 @@ mdsal-singleton-common-api mdsal-singleton-dom-api mdsal-singleton-binding-api + mdsal-singleton-common-spi -- 2.36.6