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;
10 import static org.awaitility.Awaitility.await;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
18 import akka.actor.ActorSystem;
19 import akka.actor.testkit.typed.javadsl.ActorTestKit;
20 import akka.actor.typed.ActorRef;
21 import akka.actor.typed.javadsl.Adapter;
22 import akka.actor.typed.javadsl.AskPattern;
23 import akka.cluster.ddata.ORMap;
24 import akka.cluster.ddata.ORSet;
25 import akka.cluster.ddata.typed.javadsl.DistributedData;
26 import akka.cluster.ddata.typed.javadsl.Replicator;
27 import com.typesafe.config.ConfigFactory;
28 import java.time.Duration;
29 import java.util.List;
31 import java.util.Optional;
32 import java.util.concurrent.CompletionStage;
33 import java.util.concurrent.ExecutionException;
34 import org.awaitility.Durations;
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.opendaylight.controller.eos.akka.bootstrap.command.RunningContext;
39 import org.opendaylight.controller.eos.akka.owner.supervisor.command.OwnerSupervisorCommand;
40 import org.opendaylight.controller.eos.akka.registry.candidate.CandidateRegistry;
41 import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
42 import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
43 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
44 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityName;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityType;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesInputBuilder;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityInputBuilder;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerInputBuilder;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.get.entities.output.EntitiesKey;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
55 import org.opendaylight.yangtools.concepts.Registration;
56 import org.opendaylight.yangtools.yang.common.QName;
57 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
58 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
59 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
61 public class AkkaEntityOwnershipServiceTest extends AbstractNativeEosTest {
62 static final String ENTITY_TYPE = "test";
63 static final String ENTITY_TYPE2 = "test2";
64 static final QName QNAME = QName.create("test", "2015-08-11", "foo");
66 private ActorSystem system;
67 private akka.actor.typed.ActorSystem<Void> typedSystem;
68 private AkkaEntityOwnershipService service;
69 private ActorRef<Replicator.Command> replicator;
72 public void setUp() throws Exception {
73 system = ActorSystem.create("ClusterSystem", ConfigFactory.load());
74 typedSystem = Adapter.toTyped(system);
75 replicator = DistributedData.get(typedSystem).replicator();
77 service = new AkkaEntityOwnershipService(system, CODEC_CONTEXT);
81 public void tearDown() throws InterruptedException, ExecutionException {
83 ActorTestKit.shutdown(Adapter.toTyped(system), Duration.ofSeconds(20));
87 public void testRegisterCandidate() throws Exception {
88 final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
89 final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
91 final Registration reg = service.registerCandidate(entity);
94 verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
97 service.registerCandidate(entity);
98 fail("Expected CandidateAlreadyRegisteredException");
99 } catch (final CandidateAlreadyRegisteredException e) {
101 assertEquals("getEntity", entity, e.getEntity());
104 final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE2, entityId);
105 final Registration reg2 = service.registerCandidate(entity2);
108 verifyEntityCandidateRegistered(ENTITY_TYPE2, entityId, "member-1");
112 public void testUnregisterCandidate() throws Exception {
113 final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
114 final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
116 final Registration reg = service.registerCandidate(entity);
119 verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
122 verifyEntityCandidateMissing(ENTITY_TYPE, entityId, "member-1");
124 service.registerCandidate(entity);
125 verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
129 public void testListenerRegistration() throws Exception {
131 final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
132 final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
133 final MockEntityOwnershipListener listener = new MockEntityOwnershipListener("member-1");
135 final Registration reg = service.registerListener(entity.getType(), listener);
137 assertNotNull("EntityOwnershipListenerRegistration null", reg);
139 final Registration candidate = service.registerCandidate(entity);
141 verifyListenerState(listener, entity, true, true, false);
142 final int changes = listener.getChanges().size();
147 verifyEntityCandidateMissing(ENTITY_TYPE, entityId, "member-1");
149 service.registerCandidate(entity);
150 // check listener not called when listener registration closed
151 await().pollDelay(Durations.TWO_SECONDS).until(() -> listener.getChanges().size() == changes);
155 public void testGetOwnershipState() throws Exception {
156 final DOMEntity entity = new DOMEntity(ENTITY_TYPE, "one");
158 final Registration registration = service.registerCandidate(entity);
159 verifyGetOwnershipState(service, entity, EntityOwnershipState.IS_OWNER);
161 final RunningContext runningContext = service.getRunningContext();
162 registerCandidates(runningContext.getCandidateRegistry(), entity, "member-2");
164 final ActorRef<OwnerSupervisorCommand> ownerSupervisor = runningContext.getOwnerSupervisor();
165 reachableMember(ownerSupervisor, "member-2", DEFAULT_DATACENTER);
166 unreachableMember(ownerSupervisor, "member-1", DEFAULT_DATACENTER);
167 verifyGetOwnershipState(service, entity, EntityOwnershipState.OWNED_BY_OTHER);
169 final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, "two");
170 final Optional<EntityOwnershipState> state = service.getOwnershipState(entity2);
171 assertFalse(state.isPresent());
173 unreachableMember(ownerSupervisor, "member-2", DEFAULT_DATACENTER);
174 verifyGetOwnershipState(service, entity, EntityOwnershipState.NO_OWNER);
178 public void testIsCandidateRegistered() throws Exception {
179 final DOMEntity test = new DOMEntity("test-type", "test");
181 assertFalse(service.isCandidateRegistered(test));
183 service.registerCandidate(test);
185 assertTrue(service.isCandidateRegistered(test));
189 public void testEntityRetrievalWithYiid() throws Exception {
190 final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(new NodeIdentifier(NetworkTopology.QNAME),
191 new NodeIdentifier(Topology.QNAME),
192 NodeIdentifierWithPredicates.of(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), "test"),
193 new NodeIdentifier(Node.QNAME),
194 NodeIdentifierWithPredicates.of(Node.QNAME, QName.create(Node.QNAME, "node-id"), "test://test-node"));
196 final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
198 final Registration reg = service.registerCandidate(entity);
201 verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
203 var result = service.getEntity(new GetEntityInputBuilder()
204 .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId).toIdentifier()))
205 .setType(new EntityType(ENTITY_TYPE))
210 assertEquals(result.getOwnerNode().getValue(), "member-1");
211 assertEquals(result.getCandidateNodes().get(0).getValue(), "member-1");
213 // we should not be able to retrieve the entity when using string
214 final String entityPathEncoded =
215 "/network-topology:network-topology/topology[topology-id='test']/node[node-id='test://test-node']";
217 result = service.getEntity(new GetEntityInputBuilder()
218 .setName(new EntityName(entityPathEncoded))
219 .setType(new EntityType(ENTITY_TYPE))
224 assertNull(result.getOwnerNode());
225 assertEquals(List.of(), result.getCandidateNodes());
227 final var getEntitiesResult = service.getEntities(new GetEntitiesInputBuilder().build()).get().getResult();
228 final var entities = getEntitiesResult.nonnullEntities();
229 assertEquals(1, entities.size());
230 assertTrue(entities.get(
231 new EntitiesKey(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId).toIdentifier()),
232 new EntityType(ENTITY_TYPE))).getCandidateNodes().contains(new NodeName("member-1")));
233 assertTrue(entities.get(new EntitiesKey(
234 new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId).toIdentifier()),
235 new EntityType(ENTITY_TYPE)))
236 .getOwnerNode().getValue().equals("member-1"));
238 final var getOwnerResult = service.getEntityOwner(new GetEntityOwnerInputBuilder()
239 .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId).toIdentifier()))
240 .setType(new EntityType(ENTITY_TYPE))
241 .build()).get().getResult();
243 assertEquals(getOwnerResult.getOwnerNode().getValue(), "member-1");
246 private static void verifyGetOwnershipState(final DOMEntityOwnershipService service, final DOMEntity entity,
247 final EntityOwnershipState expState) {
248 await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> {
249 assertEquals(Optional.of(expState), service.getOwnershipState(entity));
253 private void verifyEntityCandidateRegistered(final String entityType,
254 final YangInstanceIdentifier entityId,
255 final String candidateName) {
256 await().atMost(Duration.ofSeconds(5))
257 .untilAsserted(() -> doVerifyEntityCandidateRegistered(entityType, entityId, candidateName));
260 private void doVerifyEntityCandidateRegistered(final String entityType,
261 final YangInstanceIdentifier entityId,
262 final String candidateName)
263 throws ExecutionException, InterruptedException {
264 final Map<DOMEntity, ORSet<String>> entries = getCandidateData();
265 final DOMEntity entity = new DOMEntity(entityType, entityId);
266 assertTrue(entries.containsKey(entity));
267 assertTrue(entries.get(entity).getElements().contains(candidateName));
270 private void verifyEntityCandidateMissing(final String entityType,
271 final YangInstanceIdentifier entityId,
272 final String candidateName) {
273 await().atMost(Duration.ofSeconds(5))
274 .untilAsserted(() -> doVerifyEntityCandidateMissing(entityType, entityId, candidateName));
277 private void doVerifyEntityCandidateMissing(final String entityType,
278 final YangInstanceIdentifier entityId,
279 final String candidateName)
280 throws ExecutionException, InterruptedException {
281 final Map<DOMEntity, ORSet<String>> entries = getCandidateData();
282 final DOMEntity entity = new DOMEntity(entityType, entityId);
283 assertTrue(entries.containsKey(entity));
284 assertFalse(entries.get(entity).getElements().contains(candidateName));
287 private Map<DOMEntity, ORSet<String>> getCandidateData() throws ExecutionException, InterruptedException {
288 final CompletionStage<Replicator.GetResponse<ORMap<DOMEntity, ORSet<String>>>> ask =
289 AskPattern.ask(replicator, replyTo ->
290 new Replicator.Get<>(
291 CandidateRegistry.KEY,
292 Replicator.readLocal(),
294 Duration.ofSeconds(5),
295 typedSystem.scheduler());
297 final Replicator.GetResponse<ORMap<DOMEntity, ORSet<String>>> response = ask.toCompletableFuture().get();
298 assertTrue(response instanceof Replicator.GetSuccess);
300 final Replicator.GetSuccess<ORMap<DOMEntity, ORSet<String>>> success =
301 (Replicator.GetSuccess<ORMap<DOMEntity, ORSet<String>>>) response;
303 return success.get(CandidateRegistry.KEY).getEntries();