2 * Copyright (c) 2021 PANTHEON.tech, 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.eos.akka.owner.supervisor;
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;
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;
39 public class OwnerSupervisorTest extends AbstractNativeEosTest {
42 public void testCandidatePickingWhenUnreachableCandidates() throws Exception {
44 final ClusterNode node = startup(2550, Collections.singletonList("member-1"));
46 reachableMember(node, "member-2", DEFAULT_DATACENTER);
47 reachableMember(node, "member-3", DEFAULT_DATACENTER);
48 registerCandidates(node, ENTITY_1, "member-1", "member-2", "member-3");
50 final MockEntityOwnershipListener listener = registerListener(node, ENTITY_1);
51 verifyListenerState(listener, ENTITY_1,true, true, false);
53 unreachableMember(node, "member-1", DEFAULT_DATACENTER);
54 verifyListenerState(listener, ENTITY_1, true, false, true);
56 unreachableMember(node, "member-2", DEFAULT_DATACENTER);
57 verifyListenerState(listener, ENTITY_1, true, false, false);
59 unreachableMember(node, "member-3", DEFAULT_DATACENTER);
60 verifyListenerState(listener, ENTITY_1, false, false, false);
62 reachableMember(node, "member-2", DEFAULT_DATACENTER);
63 verifyListenerState(listener, ENTITY_1, true, false, false);
65 // no notification here as member-2 is already the owner
66 reachableMember(node, "member-1", DEFAULT_DATACENTER);
68 unreachableMember(node, "member-2", DEFAULT_DATACENTER);
69 verifyListenerState(listener, ENTITY_1,true, true, false);
71 ActorTestKit.shutdown(node.getActorSystem());
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"));
81 final ClusterNode node = startup(2550, Collections.singletonList("member-1"), Collections.emptyList(),
82 () -> mockedBootstrap(candidates, new HashMap<>()));
85 waitUntillOwnerPresent(node, ENTITY_1);
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");
91 final MockEntityOwnershipListener listener1 = registerListener(node, ENTITY_1);
92 final MockEntityOwnershipListener listener2 = registerListener(node, ENTITY_2);
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);
99 reachableMember(node, "member-2", DEFAULT_DATACENTER);
100 verifyListenerState(listener2, ENTITY_2, true, false, false);
102 ActorTestKit.shutdown(node.getActorSystem());
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));
112 * Initial behavior that skips initial sync and instead initializes OwnerSupervisor with provided values.
114 private static final class MockSyncer extends AbstractBehavior<OwnerSupervisorCommand> {
116 private final Map<DOMEntity, Set<String>> currentCandidates;
117 private final Map<DOMEntity, String> currentOwners;
119 private MockSyncer(final ActorContext<OwnerSupervisorCommand> context,
120 final Map<DOMEntity, Set<String>> currentCandidates,
121 final Map<DOMEntity, String> currentOwners) {
123 this.currentCandidates = currentCandidates;
124 this.currentOwners = currentOwners;
126 context.getSelf().tell(new InitialCandidateSync(null));
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));
135 public Receive<OwnerSupervisorCommand> createReceive() {
136 return newReceiveBuilder()
137 .onMessage(InitialCandidateSync.class, this::switchToSupervisor)
141 private Behavior<OwnerSupervisorCommand> switchToSupervisor(final InitialCandidateSync message) {
142 return OwnerSupervisor.create(currentCandidates, currentOwners);
147 * Bootstrap with OwnerSyncer replaced with the testing syncer behavior.
149 private static final class MockBootstrap extends AbstractBehavior<BootstrapCommand> {
151 private final ActorRef<TypeListenerRegistryCommand> listenerRegistry;
152 private final ActorRef<CandidateRegistryCommand> candidateRegistry;
153 private final ActorRef<StateCheckerCommand> ownerStateChecker;
154 private final ActorRef<OwnerSupervisorCommand> ownerSupervisor;
156 private MockBootstrap(final ActorContext<BootstrapCommand> context,
157 final Map<DOMEntity, Set<String>> currentCandidates,
158 final Map<DOMEntity, String> currentOwners) {
161 final Cluster cluster = Cluster.get(context.getSystem());
162 final String role = cluster.selfMember().getRoles().iterator().next();
164 listenerRegistry = context.spawn(EntityTypeListenerRegistry.create(role), "ListenerRegistry");
165 candidateRegistry = context.spawn(CandidateRegistry.create(), "CandidateRegistry");
166 ownerStateChecker = context.spawn(OwnerStateChecker.create(role), "OwnerStateChecker");
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"));
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));
180 public Receive<BootstrapCommand> createReceive() {
181 return newReceiveBuilder()
182 .onMessage(GetRunningContext.class, this::onGetRunningContext)
186 private Behavior<BootstrapCommand> onGetRunningContext(final GetRunningContext request) {
187 request.getReplyTo().tell(
188 new RunningContext(listenerRegistry, candidateRegistry,ownerStateChecker, ownerSupervisor));