+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.sharding;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import akka.actor.ActorRef;
+import akka.dispatch.Futures;
+import com.google.common.base.Optional;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.AbstractActorTest;
+import org.opendaylight.controller.cluster.datastore.DatastoreContext;
+import org.opendaylight.controller.cluster.datastore.exceptions.LocalShardNotFoundException;
+import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
+import org.opendaylight.controller.cluster.dom.api.LeaderLocation;
+import org.opendaylight.controller.cluster.dom.api.LeaderLocationListener;
+import org.opendaylight.controller.cluster.dom.api.LeaderLocationListenerRegistration;
+import org.opendaylight.controller.cluster.raft.LeadershipTransferFailedException;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import scala.concurrent.Future;
+import scala.concurrent.duration.FiniteDuration;
+
+public class CDSShardAccessImplTest extends AbstractActorTest {
+
+ private static final DOMDataTreeIdentifier TEST_ID =
+ new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
+
+ private CDSShardAccessImpl shardAccess;
+ private ActorContext context;
+
+ @Before
+ public void setUp() {
+ context = mock(ActorContext.class);
+ final DatastoreContext datastoreContext = DatastoreContext.newBuilder().build();
+ doReturn(Optional.of(getSystem().deadLetters())).when(context).findLocalShard(any());
+ doReturn(datastoreContext).when(context).getDatastoreContext();
+ doReturn(getSystem()).when(context).getActorSystem();
+ shardAccess = new CDSShardAccessImpl(TEST_ID, context);
+ }
+
+ @Test
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testRegisterLeaderLocationListener() {
+ final LeaderLocationListener listener1 = mock(LeaderLocationListener.class);
+
+ // first registration should be OK
+ shardAccess.registerLeaderLocationListener(listener1);
+
+ // second registration should fail with IllegalArgumentEx
+ try {
+ shardAccess.registerLeaderLocationListener(listener1);
+ fail("Should throw exception");
+ } catch (final Exception e) {
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+
+ // null listener registration should fail with NPE
+ try {
+ shardAccess.registerLeaderLocationListener(null);
+ fail("Should throw exception");
+ } catch (final Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+
+ // registering listener on closed shard access should fail with IllegalStateEx
+ final LeaderLocationListener listener2 = mock(LeaderLocationListener.class);
+ shardAccess.close();
+ try {
+ shardAccess.registerLeaderLocationListener(listener2);
+ fail("Should throw exception");
+ } catch (final Exception ex) {
+ assertTrue(ex instanceof IllegalStateException);
+ }
+ }
+
+ @Test
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testOnLeaderLocationChanged() {
+ final LeaderLocationListener listener1 = mock(LeaderLocationListener.class);
+ doThrow(new RuntimeException("Failed")).when(listener1).onLeaderLocationChanged(any());
+ final LeaderLocationListener listener2 = mock(LeaderLocationListener.class);
+ doNothing().when(listener2).onLeaderLocationChanged(any());
+ final LeaderLocationListener listener3 = mock(LeaderLocationListener.class);
+ doNothing().when(listener3).onLeaderLocationChanged(any());
+
+ final LeaderLocationListenerRegistration reg1 = shardAccess.registerLeaderLocationListener(listener1);
+ final LeaderLocationListenerRegistration reg2 = shardAccess.registerLeaderLocationListener(listener2);
+ final LeaderLocationListenerRegistration reg3 = shardAccess.registerLeaderLocationListener(listener3);
+
+ // Error in listener1 should not affect dispatching change to other listeners
+ shardAccess.onLeaderLocationChanged(LeaderLocation.LOCAL);
+ verify(listener1).onLeaderLocationChanged(eq(LeaderLocation.LOCAL));
+ verify(listener2).onLeaderLocationChanged(eq(LeaderLocation.LOCAL));
+ verify(listener3).onLeaderLocationChanged(eq(LeaderLocation.LOCAL));
+
+ // Closed listeners shouldn't see new leader location changes
+ reg1.close();
+ reg2.close();
+ shardAccess.onLeaderLocationChanged(LeaderLocation.REMOTE);
+ verify(listener3).onLeaderLocationChanged(eq(LeaderLocation.REMOTE));
+ verifyNoMoreInteractions(listener1);
+ verifyNoMoreInteractions(listener2);
+
+ // Closed shard access should not dispatch any new events
+ shardAccess.close();
+ shardAccess.onLeaderLocationChanged(LeaderLocation.UNKNOWN);
+ verifyNoMoreInteractions(listener1);
+ verifyNoMoreInteractions(listener2);
+ verifyNoMoreInteractions(listener3);
+
+ reg3.close();
+ }
+
+ @Test
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testGetShardIdentifier() {
+ assertEquals(shardAccess.getShardIdentifier(), TEST_ID);
+
+ // closed shard access should throw illegal state
+ shardAccess.close();
+ try {
+ shardAccess.getShardIdentifier();
+ fail("Exception expected");
+ } catch (final Exception e) {
+ assertTrue(e instanceof IllegalStateException);
+ }
+ }
+
+ @Test
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testGetLeaderLocation() {
+ // new shard access does not know anything about leader location
+ assertEquals(shardAccess.getLeaderLocation(), LeaderLocation.UNKNOWN);
+
+ // we start getting leader location changes notifications
+ shardAccess.onLeaderLocationChanged(LeaderLocation.LOCAL);
+ assertEquals(shardAccess.getLeaderLocation(), LeaderLocation.LOCAL);
+
+ shardAccess.onLeaderLocationChanged(LeaderLocation.REMOTE);
+ shardAccess.onLeaderLocationChanged(LeaderLocation.UNKNOWN);
+ assertEquals(shardAccess.getLeaderLocation(), LeaderLocation.UNKNOWN);
+
+ // closed shard access throws illegal state
+ shardAccess.close();
+ try {
+ shardAccess.getLeaderLocation();
+ fail("Should have failed with IllegalStateEx");
+ } catch (Exception e) {
+ assertTrue(e instanceof IllegalStateException);
+ }
+ }
+
+ @Test
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testMakeLeaderLocal() throws Exception {
+ final FiniteDuration timeout = new FiniteDuration(5, TimeUnit.SECONDS);
+ final ActorRef localShardRef = mock(ActorRef.class);
+ final Future<ActorRef> localShardRefFuture = Futures.successful(localShardRef);
+ doReturn(localShardRefFuture).when(context).findLocalShardAsync(any());
+
+ // MakeLeaderLocal will reply with success
+ doReturn(Futures.successful(null)).when(context).executeOperationAsync((ActorRef) any(), any(), any());
+ doReturn(getSystem().dispatcher()).when(context).getClientDispatcher();
+ assertEquals(waitOnAsyncTask(shardAccess.makeLeaderLocal(), timeout), null);
+
+ // MakeLeaderLocal will reply with failure
+ doReturn(Futures.failed(new LeadershipTransferFailedException("Failure")))
+ .when(context).executeOperationAsync((ActorRef) any(), any(), any());
+
+ try {
+ waitOnAsyncTask(shardAccess.makeLeaderLocal(), timeout);
+ fail("makeLeaderLocal operation should not be successful");
+ } catch (final Exception e) {
+ assertTrue(e instanceof LeadershipTransferFailedException);
+ }
+
+ // we don't even find local shard
+ doReturn(Futures.failed(new LocalShardNotFoundException("Local shard not found")))
+ .when(context).findLocalShardAsync(any());
+
+ try {
+ waitOnAsyncTask(shardAccess.makeLeaderLocal(), timeout);
+ fail("makeLeaderLocal operation should not be successful");
+ } catch (final Exception e) {
+ assertTrue(e instanceof LeadershipTransferFailedException);
+ assertTrue(e.getCause() instanceof LocalShardNotFoundException);
+ }
+
+ // closed shard access should throw IllegalStateEx
+ shardAccess.close();
+ try {
+ shardAccess.makeLeaderLocal();
+ fail("Should have thrown IllegalStateEx. ShardAccess is closed");
+ } catch (final Exception e) {
+ assertTrue(e instanceof IllegalStateException);
+ }
+ }
+}
\ No newline at end of file