Improve segmented journal actor metrics
[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.junit.jupiter.api.Assertions.assertEquals;
11 import static org.mockito.ArgumentMatchers.any;
12 import static org.mockito.Mockito.doReturn;
13 import static org.mockito.Mockito.timeout;
14 import static org.mockito.Mockito.verify;
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.util.Optional;
26 import java.util.concurrent.TimeUnit;
27 import org.junit.jupiter.api.AfterEach;
28 import org.junit.jupiter.api.BeforeEach;
29 import org.junit.jupiter.api.Test;
30 import org.junit.jupiter.api.extension.ExtendWith;
31 import org.mockito.Answers;
32 import org.mockito.Mock;
33 import org.mockito.junit.jupiter.MockitoExtension;
34 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
35 import org.opendaylight.controller.cluster.access.concepts.FrontendIdentifier;
36 import org.opendaylight.controller.cluster.access.concepts.FrontendType;
37 import org.opendaylight.controller.cluster.access.concepts.MemberName;
38 import scala.concurrent.duration.FiniteDuration;
39
40 @ExtendWith(MockitoExtension.class)
41 class ActorBehaviorTest {
42     private static final String MEMBER_1_FRONTEND_TYPE_1 = "member-1-frontend-type-1";
43     private static final FiniteDuration TIMEOUT = FiniteDuration.create(5, TimeUnit.SECONDS);
44
45     @Mock
46     private InternalCommand<BackendInfo> cmd;
47     @Mock(answer = Answers.CALLS_REAL_METHODS)
48     private ClientActorBehavior<BackendInfo> initialBehavior;
49     @Mock
50     private AbstractClientActorContext ctx;
51
52     private ActorSystem system;
53     private TestProbe probe;
54     private MockedSnapshotStore.SaveRequest saveRequest;
55     private FrontendIdentifier id;
56     private ActorRef mockedActor;
57
58     @BeforeEach
59     void beforeEach() throws Exception {
60         //persistenceId() in AbstractClientActorBehavior is final and can't be mocked
61         //use reflection to work around this
62         final var context = AbstractClientActorBehavior.class.getDeclaredField("context");
63         context.setAccessible(true);
64         context.set(initialBehavior, ctx);
65         final var persistenceId = AbstractClientActorContext.class.getDeclaredField("persistenceId");
66         persistenceId.setAccessible(true);
67         persistenceId.set(ctx, MEMBER_1_FRONTEND_TYPE_1);
68
69         system = ActorSystem.apply("system1");
70         final ActorRef storeRef = system.registerExtension(Persistence.lookup()).snapshotStoreFor(null,
71             ConfigFactory.empty());
72         probe = new TestProbe(system);
73         storeRef.tell(probe.ref(), ActorRef.noSender());
74         final MemberName name = MemberName.forName("member-1");
75         id = FrontendIdentifier.create(name, FrontendType.forName("type-1"));
76         mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
77         //handle initial actor recovery
78         saveRequest = handleRecovery(null);
79     }
80
81     @AfterEach
82     void afterEach() {
83         TestKit.shutdownActorSystem(system);
84     }
85
86     @Test
87     void testInitialBehavior() {
88         doReturn(initialBehavior).when(cmd).execute(any());
89         mockedActor.tell(cmd, ActorRef.noSender());
90         verify(cmd, timeout(1000)).execute(initialBehavior);
91     }
92
93     @Test
94     void testCommandStashing() {
95         system.stop(mockedActor);
96         mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
97         doReturn(initialBehavior).when(cmd).execute(any());
98         //send messages before recovery is completed
99         mockedActor.tell(cmd, ActorRef.noSender());
100         mockedActor.tell(cmd, ActorRef.noSender());
101         mockedActor.tell(cmd, ActorRef.noSender());
102         //complete recovery
103         handleRecovery(null);
104         verify(cmd, timeout(1000).times(3)).execute(initialBehavior);
105     }
106
107     @Test
108     void testRecoveryAfterRestart() {
109         system.stop(mockedActor);
110         mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
111         final MockedSnapshotStore.SaveRequest newSaveRequest =
112                 handleRecovery(new SelectedSnapshot(saveRequest.getMetadata(), saveRequest.getSnapshot()));
113         assertEquals(MEMBER_1_FRONTEND_TYPE_1, newSaveRequest.getMetadata().persistenceId());
114     }
115
116     @Test
117     void testRecoveryAfterRestartFrontendIdMismatch() {
118         system.stop(mockedActor);
119         //start actor again
120         mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
121         probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
122         //offer snapshot with incorrect client id
123         final SnapshotMetadata metadata = saveRequest.getMetadata();
124         final FrontendIdentifier anotherFrontend = FrontendIdentifier.create(MemberName.forName("another"),
125                 FrontendType.forName("type-2"));
126         final ClientIdentifier incorrectClientId = ClientIdentifier.create(anotherFrontend, 0);
127         probe.watch(mockedActor);
128         probe.reply(Optional.of(new SelectedSnapshot(metadata, incorrectClientId)));
129         //actor should be stopped
130         probe.expectTerminated(mockedActor, TIMEOUT);
131     }
132
133     @Test
134     void testRecoveryAfterRestartSaveSnapshotFail() {
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(new RuntimeException("save failed"));
142         probe.expectMsgClass(MockedSnapshotStore.DeleteByMetadataRequest.class);
143         probe.expectTerminated(mockedActor, TIMEOUT);
144     }
145
146     @Test
147     void testRecoveryAfterRestartDeleteSnapshotsFail() {
148         system.stop(mockedActor);
149         mockedActor = system.actorOf(MockedActor.props(id, initialBehavior));
150         probe.watch(mockedActor);
151         probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
152         probe.reply(Optional.empty());
153         probe.expectMsgClass(MockedSnapshotStore.SaveRequest.class);
154         probe.reply(Void.TYPE);
155         probe.expectMsgClass(MockedSnapshotStore.DeleteByCriteriaRequest.class);
156         probe.reply(new RuntimeException("delete failed"));
157         //actor shouldn't terminate
158         probe.expectNoMessage();
159     }
160
161     private MockedSnapshotStore.SaveRequest handleRecovery(final SelectedSnapshot savedState) {
162         probe.expectMsgClass(MockedSnapshotStore.LoadRequest.class);
163         //offer snapshot
164         probe.reply(Optional.ofNullable(savedState));
165         final MockedSnapshotStore.SaveRequest nextSaveRequest =
166                 probe.expectMsgClass(MockedSnapshotStore.SaveRequest.class);
167         probe.reply(Void.TYPE);
168         //check old snapshots deleted
169         probe.expectMsgClass(MockedSnapshotStore.DeleteByCriteriaRequest.class);
170         probe.reply(Void.TYPE);
171         return nextSaveRequest;
172     }
173
174     private static class MockedActor extends AbstractClientActor {
175         private final ClientActorBehavior<?> initialBehavior;
176         private final ClientActorConfig mockConfig = AccessClientUtil.newMockClientActorConfig();
177
178         private static Props props(final FrontendIdentifier frontendId, final ClientActorBehavior<?> initialBehavior) {
179             return Props.create(MockedActor.class, () -> new MockedActor(frontendId, initialBehavior));
180         }
181
182         MockedActor(final FrontendIdentifier frontendId, final ClientActorBehavior<?> initialBehavior) {
183             super(frontendId);
184             this.initialBehavior = initialBehavior;
185         }
186
187         @Override
188         protected ClientActorBehavior<?> initialBehavior(final ClientActorContext context) {
189             return initialBehavior;
190         }
191
192         @Override
193         protected ClientActorConfig getClientActorConfig() {
194             return mockConfig;
195         }
196     }
197 }