2 * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.cluster.access.client;
10 import static org.mockito.ArgumentMatchers.any;
11 import static org.mockito.Mockito.mock;
12 import static org.mockito.Mockito.timeout;
13 import static org.mockito.Mockito.verify;
14 import static org.mockito.Mockito.when;
16 import akka.actor.ActorRef;
17 import akka.actor.ActorSystem;
18 import akka.actor.Props;
19 import akka.persistence.Persistence;
20 import akka.persistence.SelectedSnapshot;
21 import akka.persistence.SnapshotMetadata;
22 import akka.testkit.TestProbe;
23 import akka.testkit.javadsl.TestKit;
24 import com.typesafe.config.ConfigFactory;
25 import java.lang.reflect.Field;
26 import java.util.Optional;
27 import java.util.concurrent.TimeUnit;
28 import org.junit.After;
29 import org.junit.Assert;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
33 import org.opendaylight.controller.cluster.access.concepts.FrontendIdentifier;
34 import org.opendaylight.controller.cluster.access.concepts.FrontendType;
35 import org.opendaylight.controller.cluster.access.concepts.MemberName;
36 import scala.concurrent.duration.Duration;
37 import scala.concurrent.duration.FiniteDuration;
39 public class ActorBehaviorTest {
41 private static final String MEMBER_1_FRONTEND_TYPE_1 = "member-1-frontend-type-1";
42 private static final FiniteDuration TIMEOUT = Duration.apply(5, TimeUnit.SECONDS);
44 private ActorSystem system;
45 private TestProbe probe;
46 private ClientActorBehavior<BackendInfo> initialBehavior;
47 private MockedSnapshotStore.SaveRequest saveRequest;
48 private FrontendIdentifier id;
49 private ActorRef mockedActor;
52 public void setUp() throws Exception {
53 initialBehavior = createInitialBehaviorMock();
54 system = ActorSystem.apply("system1");
55 final ActorRef storeRef = system.registerExtension(Persistence.lookup()).snapshotStoreFor(null,
56 ConfigFactory.empty());
57 probe = new TestProbe(system);
58 storeRef.tell(probe.ref(), ActorRef.noSender());
59 final MemberName name = MemberName.forName("member-1");
60 id = FrontendIdentifier.create(name, FrontendType.forName("type-1"));
61 mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
62 //handle initial actor recovery
63 saveRequest = handleRecovery(null);
67 public void tearDown() {
68 TestKit.shutdownActorSystem(system);
72 public void testInitialBehavior() {
73 final InternalCommand<BackendInfo> cmd = mock(InternalCommand.class);
74 when(cmd.execute(any())).thenReturn(initialBehavior);
75 mockedActor.tell(cmd, ActorRef.noSender());
76 verify(cmd, timeout(1000)).execute(initialBehavior);
80 public void testCommandStashing() {
81 system.stop(mockedActor);
82 mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
83 final InternalCommand<BackendInfo> cmd = mock(InternalCommand.class);
84 when(cmd.execute(any())).thenReturn(initialBehavior);
85 //send messages before recovery is completed
86 mockedActor.tell(cmd, ActorRef.noSender());
87 mockedActor.tell(cmd, ActorRef.noSender());
88 mockedActor.tell(cmd, ActorRef.noSender());
91 verify(cmd, timeout(1000).times(3)).execute(initialBehavior);
95 public void testRecoveryAfterRestart() {
96 system.stop(mockedActor);
97 mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
98 final MockedSnapshotStore.SaveRequest newSaveRequest =
99 handleRecovery(new SelectedSnapshot(saveRequest.getMetadata(), saveRequest.getSnapshot()));
100 Assert.assertEquals(MEMBER_1_FRONTEND_TYPE_1, newSaveRequest.getMetadata().persistenceId());
104 public void testRecoveryAfterRestartFrontendIdMismatch() {
105 system.stop(mockedActor);
107 mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
108 probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
109 //offer snapshot with incorrect client id
110 final SnapshotMetadata metadata = saveRequest.getMetadata();
111 final FrontendIdentifier anotherFrontend = FrontendIdentifier.create(MemberName.forName("another"),
112 FrontendType.forName("type-2"));
113 final ClientIdentifier incorrectClientId = ClientIdentifier.create(anotherFrontend, 0);
114 probe.watch(mockedActor);
115 probe.reply(Optional.of(new SelectedSnapshot(metadata, incorrectClientId)));
116 //actor should be stopped
117 probe.expectTerminated(mockedActor, TIMEOUT);
121 public void testRecoveryAfterRestartSaveSnapshotFail() {
122 system.stop(mockedActor);
123 mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
124 probe.watch(mockedActor);
125 probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
126 probe.reply(Optional.empty());
127 probe.expectMsgClass(MockedSnapshotStore.SaveRequest.class);
128 probe.reply(new RuntimeException("save failed"));
129 probe.expectMsgClass(MockedSnapshotStore.DeleteByMetadataRequest.class);
130 probe.expectTerminated(mockedActor, TIMEOUT);
134 public void testRecoveryAfterRestartDeleteSnapshotsFail() {
135 system.stop(mockedActor);
136 mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
137 probe.watch(mockedActor);
138 probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
139 probe.reply(Optional.empty());
140 probe.expectMsgClass(MockedSnapshotStore.SaveRequest.class);
141 probe.reply(Void.TYPE);
142 probe.expectMsgClass(MockedSnapshotStore.DeleteByCriteriaRequest.class);
143 probe.reply(new RuntimeException("delete failed"));
144 //actor shouldn't terminate
145 probe.expectNoMessage();
148 @SuppressWarnings("unchecked")
149 private static ClientActorBehavior<BackendInfo> createInitialBehaviorMock() throws Exception {
150 final ClientActorBehavior<BackendInfo> initialBehavior = mock(ClientActorBehavior.class);
151 //persistenceId() in AbstractClientActorBehavior is final and can't be mocked
152 //use reflection to work around this
153 final Field context = AbstractClientActorBehavior.class.getDeclaredField("context");
154 context.setAccessible(true);
155 final AbstractClientActorContext ctx = mock(AbstractClientActorContext.class);
156 context.set(initialBehavior, ctx);
157 final Field persistenceId = AbstractClientActorContext.class.getDeclaredField("persistenceId");
158 persistenceId.setAccessible(true);
159 persistenceId.set(ctx, MEMBER_1_FRONTEND_TYPE_1);
160 return initialBehavior;
163 private MockedSnapshotStore.SaveRequest handleRecovery(final SelectedSnapshot savedState) {
164 probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
166 probe.reply(Optional.ofNullable(savedState));
167 final MockedSnapshotStore.SaveRequest nextSaveRequest =
168 probe.expectMsgClass(MockedSnapshotStore.SaveRequest.class);
169 probe.reply(Void.TYPE);
170 //check old snapshots deleted
171 probe.expectMsgClass(MockedSnapshotStore.DeleteByCriteriaRequest.class);
172 probe.reply(Void.TYPE);
173 return nextSaveRequest;
176 private static class MockedActor extends AbstractClientActor {
178 private final ClientActorBehavior<?> initialBehavior;
179 private final ClientActorConfig mockConfig = AccessClientUtil.newMockClientActorConfig();
181 private static Props props(final FrontendIdentifier frontendId, final ClientActorBehavior<?> initialBehavior) {
182 return Props.create(MockedActor.class, () -> new MockedActor(frontendId, initialBehavior));
185 MockedActor(final FrontendIdentifier frontendId, final ClientActorBehavior<?> initialBehavior) {
187 this.initialBehavior = initialBehavior;
191 protected ClientActorBehavior<?> initialBehavior(final ClientActorContext context) {
192 return initialBehavior;
196 protected ClientActorConfig getClientActorConfig() {