61747622d1c8343e13762e48f6aa0bb47e61a194
[controller.git] / opendaylight / md-sal / eos-dom-akka / src / test / java / org / opendaylight / controller / eos / akka / owner / supervisor / OwnerSupervisorTest.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, 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.eos.akka.owner.supervisor;
9
10 import akka.actor.testkit.typed.javadsl.ActorTestKit;
11 import akka.actor.typed.ActorRef;
12 import akka.actor.typed.Behavior;
13 import akka.actor.typed.javadsl.AbstractBehavior;
14 import akka.actor.typed.javadsl.ActorContext;
15 import akka.actor.typed.javadsl.Behaviors;
16 import akka.actor.typed.javadsl.Receive;
17 import akka.cluster.typed.Cluster;
18 import akka.cluster.typed.ClusterSingleton;
19 import akka.cluster.typed.SingletonActor;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Set;
24 import org.junit.Test;
25 import org.opendaylight.controller.eos.akka.AbstractNativeEosTest;
26 import org.opendaylight.controller.eos.akka.bootstrap.command.BootstrapCommand;
27 import org.opendaylight.controller.eos.akka.bootstrap.command.GetRunningContext;
28 import org.opendaylight.controller.eos.akka.bootstrap.command.RunningContext;
29 import org.opendaylight.controller.eos.akka.owner.checker.OwnerStateChecker;
30 import org.opendaylight.controller.eos.akka.owner.checker.command.StateCheckerCommand;
31 import org.opendaylight.controller.eos.akka.owner.supervisor.command.InitialCandidateSync;
32 import org.opendaylight.controller.eos.akka.owner.supervisor.command.OwnerSupervisorCommand;
33 import org.opendaylight.controller.eos.akka.registry.candidate.CandidateRegistry;
34 import org.opendaylight.controller.eos.akka.registry.candidate.command.CandidateRegistryCommand;
35 import org.opendaylight.controller.eos.akka.registry.listener.type.EntityTypeListenerRegistry;
36 import org.opendaylight.controller.eos.akka.registry.listener.type.command.TypeListenerRegistryCommand;
37 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
38
39 public class OwnerSupervisorTest extends AbstractNativeEosTest {
40
41     @Test
42     public void testCandidatePickingWhenUnreachableCandidates() throws Exception {
43
44         final ClusterNode node = startup(2550, Collections.singletonList("member-1"));
45         try {
46             reachableMember(node, "member-2");
47             reachableMember(node, "member-3");
48             registerCandidates(node, ENTITY_1, "member-1", "member-2", "member-3");
49
50             final MockEntityOwnershipListener listener = registerListener(node, ENTITY_1);
51             verifyListenerState(listener, ENTITY_1,true, true, false);
52
53             unreachableMember(node, "member-1");
54             verifyListenerState(listener, ENTITY_1, true, false, true);
55
56             unreachableMember(node, "member-2");
57             verifyListenerState(listener, ENTITY_1, true, false, false);
58
59             unreachableMember(node, "member-3");
60             verifyListenerState(listener, ENTITY_1, false, false, false);
61
62             reachableMember(node, "member-2");
63             verifyListenerState(listener, ENTITY_1, true, false, false);
64
65             // no notification here as member-2 is already the owner
66             reachableMember(node, "member-1");
67
68             unreachableMember(node, "member-2");
69             verifyListenerState(listener, ENTITY_1,true, true, false);
70         } finally {
71             ActorTestKit.shutdown(node.getActorSystem());
72         }
73     }
74
75     @Test
76     public void testSupervisorInitWithMissingOwners() throws Exception {
77         final Map<DOMEntity, Set<String>> candidates = new HashMap<>();
78         candidates.put(ENTITY_1, Set.of("member-1"));
79         candidates.put(ENTITY_2, Set.of("member-2"));
80
81         final ClusterNode node = startup(2550, Collections.singletonList("member-1"), Collections.emptyList(),
82                 () -> mockedBootstrap(candidates, new HashMap<>()));
83
84         try {
85             waitUntillOwnerPresent(node, ENTITY_1);
86
87             // also do a proper register so the listener from the type lister actor are spawned
88             registerCandidates(node, ENTITY_1, "member-1");
89             registerCandidates(node, ENTITY_2, "member-2");
90
91             final MockEntityOwnershipListener listener1 = registerListener(node, ENTITY_1);
92             final MockEntityOwnershipListener listener2 = registerListener(node, ENTITY_2);
93
94             // first entity should have correctly assigned owner as its reachable
95             verifyListenerState(listener1, ENTITY_1, true, true, false);
96             // this one could not be assigned during init as we dont have member-2 thats reachable
97             verifyListenerState(listener2, ENTITY_2, false, false, false);
98
99             reachableMember(node, "member-2");
100             verifyListenerState(listener2, ENTITY_2, true, false, false);
101         } finally {
102             ActorTestKit.shutdown(node.getActorSystem());
103         }
104     }
105
106     private static Behavior<BootstrapCommand> mockedBootstrap(final Map<DOMEntity, Set<String>> currentCandidates,
107                                                               final Map<DOMEntity, String> currentOwners) {
108         return Behaviors.setup(context -> MockBootstrap.create(currentCandidates, currentOwners));
109     }
110
111     /**
112      * Initial behavior that skips initial sync and instead initializes OwnerSupervisor with provided values.
113      */
114     private static final class MockSyncer extends AbstractBehavior<OwnerSupervisorCommand> {
115
116         private final Map<DOMEntity, Set<String>> currentCandidates;
117         private final Map<DOMEntity, String> currentOwners;
118
119         private MockSyncer(final ActorContext<OwnerSupervisorCommand> context,
120                           final Map<DOMEntity, Set<String>> currentCandidates,
121                           final Map<DOMEntity, String> currentOwners) {
122             super(context);
123             this.currentCandidates = currentCandidates;
124             this.currentOwners = currentOwners;
125
126             context.getSelf().tell(new InitialCandidateSync(null));
127         }
128
129         public static Behavior<OwnerSupervisorCommand> create(final Map<DOMEntity, Set<String>> currentCandidates,
130                                                               final Map<DOMEntity, String> currentOwners) {
131             return Behaviors.setup(ctx -> new MockSyncer(ctx, currentCandidates, currentOwners));
132         }
133
134         @Override
135         public Receive<OwnerSupervisorCommand> createReceive() {
136             return newReceiveBuilder()
137                     .onMessage(InitialCandidateSync.class, this::switchToSupervisor)
138                     .build();
139         }
140
141         private Behavior<OwnerSupervisorCommand> switchToSupervisor(final InitialCandidateSync message) {
142             return OwnerSupervisor.create(currentCandidates, currentOwners);
143         }
144     }
145
146     /**
147      * Bootstrap with OwnerSyncer replaced with the testing syncer behavior.
148      */
149     private static final class MockBootstrap extends AbstractBehavior<BootstrapCommand> {
150
151         private final ActorRef<TypeListenerRegistryCommand> listenerRegistry;
152         private final ActorRef<CandidateRegistryCommand> candidateRegistry;
153         private final ActorRef<StateCheckerCommand> ownerStateChecker;
154         private final ActorRef<OwnerSupervisorCommand> ownerSupervisor;
155
156         private MockBootstrap(final ActorContext<BootstrapCommand> context,
157                               final Map<DOMEntity, Set<String>> currentCandidates,
158                               final Map<DOMEntity, String> currentOwners) {
159             super(context);
160
161             final Cluster cluster = Cluster.get(context.getSystem());
162             final String role = cluster.selfMember().getRoles().iterator().next();
163
164             listenerRegistry = context.spawn(EntityTypeListenerRegistry.create(role), "ListenerRegistry");
165             candidateRegistry = context.spawn(CandidateRegistry.create(), "CandidateRegistry");
166             ownerStateChecker = context.spawn(OwnerStateChecker.create(role), "OwnerStateChecker");
167
168             final ClusterSingleton clusterSingleton = ClusterSingleton.get(context.getSystem());
169             // start the initial sync behavior that switches to the regular one after syncing
170             ownerSupervisor = clusterSingleton.init(SingletonActor.of(
171                     MockSyncer.create(currentCandidates, currentOwners), "OwnerSupervisor"));
172         }
173
174         public static Behavior<BootstrapCommand> create(final Map<DOMEntity, Set<String>> currentCandidates,
175                                                         final Map<DOMEntity, String> currentOwners) {
176             return Behaviors.setup(ctx -> new MockBootstrap(ctx, currentCandidates, currentOwners));
177         }
178
179         @Override
180         public Receive<BootstrapCommand> createReceive() {
181             return newReceiveBuilder()
182                     .onMessage(GetRunningContext.class, this::onGetRunningContext)
183                     .build();
184         }
185
186         private Behavior<BootstrapCommand> onGetRunningContext(final GetRunningContext request) {
187             request.getReplyTo().tell(
188                     new RunningContext(listenerRegistry, candidateRegistry,ownerStateChecker, ownerSupervisor));
189             return this;
190         }
191     }
192
193 }