Integrate MRI projects for Neon
[controller.git] / opendaylight / md-sal / cds-access-client / src / test / java / org / opendaylight / controller / cluster / access / client / ActorBehaviorTest.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.cluster.access.client;
9
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;
15
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;
38
39 public class ActorBehaviorTest {
40
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);
43
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;
50
51     @Before
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);
64     }
65
66     @After
67     public void tearDown() {
68         TestKit.shutdownActorSystem(system);
69     }
70
71     @Test
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);
77     }
78
79     @Test
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());
89         //complete recovery
90         handleRecovery(null);
91         verify(cmd, timeout(1000).times(3)).execute(initialBehavior);
92     }
93
94     @Test
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());
101     }
102
103     @Test
104     public void testRecoveryAfterRestartFrontendIdMismatch() {
105         system.stop(mockedActor);
106         //start actor again
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);
118     }
119
120     @Test
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);
131     }
132
133     @Test
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();
146     }
147
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;
161     }
162
163     private MockedSnapshotStore.SaveRequest handleRecovery(final SelectedSnapshot savedState) {
164         probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
165         //offer snapshot
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;
174     }
175
176     private static class MockedActor extends AbstractClientActor {
177
178         private final ClientActorBehavior<?> initialBehavior;
179         private final ClientActorConfig mockConfig = AccessClientUtil.newMockClientActorConfig();
180
181         private static Props props(final FrontendIdentifier frontendId, final ClientActorBehavior<?> initialBehavior) {
182             return Props.create(MockedActor.class, () -> new MockedActor(frontendId, initialBehavior));
183         }
184
185         MockedActor(final FrontendIdentifier frontendId, final ClientActorBehavior<?> initialBehavior) {
186             super(frontendId);
187             this.initialBehavior = initialBehavior;
188         }
189
190         @Override
191         protected ClientActorBehavior<?> initialBehavior(final ClientActorContext context) {
192             return initialBehavior;
193         }
194
195         @Override
196         protected ClientActorConfig getClientActorConfig() {
197             return mockConfig;
198         }
199     }
200
201 }