<artifactId>mdsal-singleton-common-api</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-singleton-common-spi</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.opendaylight.mdsal</groupId>
<artifactId>mdsal-singleton-dom-api</artifactId>
<artifactId>mockito-configuration</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<scm>
--- /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.mdsal</groupId>
+ <artifactId>singleton-service</artifactId>
+ <version>2.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>mdsal-singleton-common-spi</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-eos-common-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-singleton-common-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>concepts</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <scm>
+ <connection>scm:git:http://git.opendaylight.org/gerrit/mdsal.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/mdsal.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://wiki.opendaylight.org/view/MD-SAL:Main</url>
+ </scm>
+
+ <!--
+ Maven Site Configuration
+
+ The following configuration is necessary for maven-site-plugin to
+ correctly identify the correct deployment path for OpenDaylight Maven
+ sites.
+ -->
+ <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+ <distributionManagement>
+ <site>
+ <id>opendaylight-site</id>
+ <url>${nexus.site.url}/${project.artifactId}/</url>
+ </site>
+ </distributionManagement>
+
+</project>
--- /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.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
+}
* 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;
/**
--- /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.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 <P> the instance identifier path type
+ * @param <E> the GenericEntity type
+ * @param <C> the GenericEntityOwnershipChange type
+ * @param <G> the GenericEntityOwnershipListener type
+ * @param <S> the GenericEntityOwnershipService type
+ */
+@VisibleForTesting
+final class ClusterSingletonServiceGroupImpl<P extends Path<P>, E extends GenericEntity<P>,
+ C extends GenericEntityOwnershipChange<P, E>,
+ G extends GenericEntityOwnershipListener<P, C>,
+ S extends GenericEntityOwnershipService<P, E, G>>
+ implements ClusterSingletonServiceGroup<P, E, C> {
+
+ 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<ClusterSingletonServiceRegistrationDelegator> serviceGroup = new LinkedList<>();
+ private final ConcurrentMap<String, ClusterSingletonServiceGroup<P, E, C>> allServiceGroups;
+
+ /* EOS Candidate Registrations */
+ private GenericEntityOwnershipCandidateRegistration<P, E> serviceEntityCandidateReg;
+ private GenericEntityOwnershipCandidateRegistration<P, E> 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<String, ClusterSingletonServiceGroup<P, E, C>> 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<List<Void>> closeClusterSingletonGroup() {
+ LOG.debug("Close method for service Provider {}", clusterSingletonGroupIdentifier);
+ boolean needReleaseLock = false;
+ final ListenableFuture<List<Void>> 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<ListenableFuture<Void>> 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<ListenableFuture<Void>> serviceCloseFutureList = new ArrayList<>();
+ if (hasOwnership) {
+ Verify.verify(asyncCloseEntityCandidateReg != null);
+ for (final ClusterSingletonServiceRegistrationDelegator service : serviceGroup) {
+ serviceCloseFutureList.add(service.closeServiceInstance());
+ }
+ }
+
+ final ListenableFuture<List<Void>> 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<List<Void>> closeFutureList = this.closeClusterSingletonGroup();
+ Futures.addCallback(closeFutureList, new FutureCallback<List<Void>>() {
+
+ @Override
+ public void onSuccess(final List<Void> 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<List<Void>> newAsyncCloseCallback(@Nullable final Semaphore semaphore) {
+ final Consumer<Throwable> 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<List<Void>>() {
+
+ @Override
+ public void onSuccess(final List<Void> result) {
+ closeEntityCandidateRegistration.accept(null);
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ closeEntityCandidateRegistration.accept(t);
+ }
+ };
+ }
+
+}
* 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}
--- /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.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<TestInstanceIdentifier,GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity>> mockEosListener;
+
+ @Mock
+ private GenericEntityOwnershipService<TestInstanceIdentifier,TestEntity, GenericEntityOwnershipListener<TestInstanceIdentifier,GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity>>> mockEosService;
+
+ private ClusterSingletonServiceGroupImpl<TestInstanceIdentifier,TestEntity,GenericEntityOwnershipChange<TestInstanceIdentifier,TestEntity>,
+ GenericEntityOwnershipListener<TestInstanceIdentifier, GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity>>,
+ GenericEntityOwnershipService<TestInstanceIdentifier, TestEntity, GenericEntityOwnershipListener<TestInstanceIdentifier,
+ GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity>>>> 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<String, ClusterSingletonServiceGroup<?, ?, ?>> 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<TestInstanceIdentifier, TestEntity> getEntityToMaster() {
+ return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(false, true, true));
+ }
+
+ private GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity> getEntityToSlave() {
+ return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(true, false, true));
+ }
+
+ private GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity> getEntityToSlaveNoMaster() {
+ return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(true, false, false));
+ }
+
+ private GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity> getDoubleEntityToMaster() {
+ return new GenericEntityOwnershipChange<>(closeEntity, EntityOwnershipChangeState.from(false, true, true));
+ }
+
+ private GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity> getDoubleEntityToSlave() {
+ return new GenericEntityOwnershipChange<>(closeEntity, EntityOwnershipChangeState.from(true, false, true));
+ }
+
+ private GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity> getInitDoubleEntityToSlave() {
+ return new GenericEntityOwnershipChange<>(closeEntity, EntityOwnershipChangeState.from(false, false, true));
+ }
+
+ private GenericEntityOwnershipChange<TestInstanceIdentifier, TestEntity> getEntityToJeopardy() {
+ return new GenericEntityOwnershipChange<>(mainEntity, EntityOwnershipChangeState.from(false, false, false), true);
+ }
+
+}
--- /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.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);
+ }
+}
--- /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.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<TestInstanceIdentifier> {
+ 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));
+ }
+}
--- /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.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<TestInstanceIdentifier> {
+
+ static final TestInstanceIdentifier EMPTY_INSTANCE = new TestInstanceIdentifier(ImmutableList.of());
+
+ private final ImmutableList<String> path;
+
+ /**
+ * {@link TestInstanceIdentifier} constructor
+ *
+ * @param path - path
+ */
+ public TestInstanceIdentifier(final Iterable<? extends TestInstanceIdentifier> path) {
+ Preconditions.checkArgument(path != null);
+ final List<String> 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);
+ }
+
+}
<module>mdsal-singleton-common-api</module>
<module>mdsal-singleton-dom-api</module>
<module>mdsal-singleton-binding-api</module>
+ <module>mdsal-singleton-common-spi</module>
</modules>