Add an actor for entity rpc execution.
[controller.git] / opendaylight / md-sal / eos-dom-akka / src / test / java / org / opendaylight / controller / eos / akka / AkkaEntityOwnershipServiceTest.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;
9
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;
17
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.Map;
30 import java.util.Optional;
31 import java.util.concurrent.CompletionStage;
32 import java.util.concurrent.ExecutionException;
33 import org.awaitility.Durations;
34 import org.junit.After;
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.opendaylight.controller.eos.akka.bootstrap.command.RunningContext;
38 import org.opendaylight.controller.eos.akka.owner.supervisor.command.OwnerSupervisorCommand;
39 import org.opendaylight.controller.eos.akka.registry.candidate.CandidateRegistry;
40 import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
41 import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
42 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
43 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
44 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
45 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityName;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityType;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesInputBuilder;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesOutput;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityInputBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOutput;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerInputBuilder;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerOutput;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.get.entities.output.EntitiesKey;
56 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
57 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
58 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
59 import org.opendaylight.yangtools.yang.common.QName;
60 import org.opendaylight.yangtools.yang.common.RpcResult;
61 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
62 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
63 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
64
65 public class AkkaEntityOwnershipServiceTest extends AbstractNativeEosTest {
66     static final String ENTITY_TYPE = "test";
67     static final String ENTITY_TYPE2 = "test2";
68     static final QName QNAME = QName.create("test", "2015-08-11", "foo");
69
70     private ActorSystem system;
71     private akka.actor.typed.ActorSystem<Void> typedSystem;
72     private AkkaEntityOwnershipService service;
73     private ActorRef<Replicator.Command> replicator;
74
75     @Before
76     public void setUp() throws Exception {
77         system = ActorSystem.create("ClusterSystem", ConfigFactory.load());
78         typedSystem = Adapter.toTyped(this.system);
79         replicator = DistributedData.get(typedSystem).replicator();
80
81         service = new AkkaEntityOwnershipService(system, CODEC_CONTEXT);
82     }
83
84     @After
85     public void tearDown() throws InterruptedException, ExecutionException {
86         service.close();
87         ActorTestKit.shutdown(Adapter.toTyped(system), Duration.ofSeconds(20));
88     }
89
90     @Test
91     public void testRegisterCandidate() throws Exception {
92         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
93         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
94
95         final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
96
97         verifyEntityOwnershipCandidateRegistration(entity, reg);
98         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
99
100         try {
101             service.registerCandidate(entity);
102             fail("Expected CandidateAlreadyRegisteredException");
103         } catch (final CandidateAlreadyRegisteredException e) {
104             // expected
105             assertEquals("getEntity", entity, e.getEntity());
106         }
107
108         final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE2, entityId);
109         final DOMEntityOwnershipCandidateRegistration reg2 = service.registerCandidate(entity2);
110
111         verifyEntityOwnershipCandidateRegistration(entity2, reg2);
112         verifyEntityCandidateRegistered(ENTITY_TYPE2, entityId, "member-1");
113     }
114
115     @Test
116     public void testUnregisterCandidate() throws Exception {
117         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
118         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
119
120         final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
121
122         verifyEntityOwnershipCandidateRegistration(entity, reg);
123         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
124
125         reg.close();
126         verifyEntityCandidateMissing(ENTITY_TYPE, entityId, "member-1");
127
128         service.registerCandidate(entity);
129         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
130     }
131
132     @Test
133     public void testListenerRegistration() throws Exception {
134
135         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
136         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
137         final MockEntityOwnershipListener listener = new MockEntityOwnershipListener("member-1");
138
139         final DOMEntityOwnershipListenerRegistration reg = service.registerListener(entity.getType(), listener);
140
141         assertNotNull("EntityOwnershipListenerRegistration null", reg);
142         assertEquals("getEntityType", entity.getType(), reg.getEntityType());
143         assertEquals("getInstance", listener, reg.getInstance());
144
145         final DOMEntityOwnershipCandidateRegistration candidate = service.registerCandidate(entity);
146
147         verifyListenerState(listener, entity, true, true, false);
148         final int changes = listener.getChanges().size();
149
150         reg.close();
151         candidate.close();
152
153         verifyEntityCandidateMissing(ENTITY_TYPE, entityId, "member-1");
154
155         service.registerCandidate(entity);
156         // check listener not called when listener registration closed
157         await().pollDelay(Durations.TWO_SECONDS).until(() -> listener.getChanges().size() == changes);
158     }
159
160     @Test
161     public void testGetOwnershipState() throws Exception {
162         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, "one");
163
164         final DOMEntityOwnershipCandidateRegistration registration = service.registerCandidate(entity);
165         verifyGetOwnershipState(service, entity, EntityOwnershipState.IS_OWNER);
166
167         final RunningContext runningContext = service.getRunningContext();
168         registerCandidates(runningContext.getCandidateRegistry(), entity, "member-2");
169
170         final ActorRef<OwnerSupervisorCommand> ownerSupervisor = runningContext.getOwnerSupervisor();
171         reachableMember(ownerSupervisor, "member-2", DEFAULT_DATACENTER);
172         unreachableMember(ownerSupervisor, "member-1", DEFAULT_DATACENTER);
173         verifyGetOwnershipState(service, entity, EntityOwnershipState.OWNED_BY_OTHER);
174
175         final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, "two");
176         final Optional<EntityOwnershipState> state = service.getOwnershipState(entity2);
177         assertFalse(state.isPresent());
178
179         unreachableMember(ownerSupervisor, "member-2", DEFAULT_DATACENTER);
180         verifyGetOwnershipState(service, entity, EntityOwnershipState.NO_OWNER);
181     }
182
183     @Test
184     public void testIsCandidateRegistered() throws Exception {
185         final DOMEntity test = new DOMEntity("test-type", "test");
186
187         assertFalse(service.isCandidateRegistered(test));
188
189         service.registerCandidate(test);
190
191         assertTrue(service.isCandidateRegistered(test));
192     }
193
194     @Test
195     public void testEntityRetrievalWithYiid() throws Exception {
196         final YangInstanceIdentifier entityId = YangInstanceIdentifier.create(new NodeIdentifier(NetworkTopology.QNAME),
197                 new NodeIdentifier(Topology.QNAME),
198                 NodeIdentifierWithPredicates.of(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), "test"),
199                 new NodeIdentifier(Node.QNAME),
200                 NodeIdentifierWithPredicates.of(Node.QNAME, QName.create(Node.QNAME, "node-id"), "test://test-node"));
201
202         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
203
204         final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
205
206         verifyEntityOwnershipCandidateRegistration(entity, reg);
207         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
208
209         RpcResult<GetEntityOutput> getEntityResult = service.getEntity(new GetEntityInputBuilder()
210                 .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
211                 .setType(new EntityType(ENTITY_TYPE))
212                 .build())
213                 .get();
214
215         assertEquals(getEntityResult.getResult().getOwnerNode().getValue(), "member-1");
216         assertEquals(getEntityResult.getResult().getCandidateNodes().get(0).getValue(), "member-1");
217
218         // we should not be able to retrieve the entity when using string
219         final String entityPathEncoded =
220                 "/network-topology:network-topology/topology[topology-id='test']/node[node-id='test://test-node']";
221
222         getEntityResult = service.getEntity(new GetEntityInputBuilder()
223                 .setName(new EntityName(entityPathEncoded))
224                 .setType(new EntityType(ENTITY_TYPE))
225                 .build())
226                 .get();
227
228         assertNull(getEntityResult.getResult().getOwnerNode());
229         assertTrue(getEntityResult.getResult().getCandidateNodes().isEmpty());
230
231         final GetEntitiesOutput getEntitiesResult =
232                 service.getEntities(new GetEntitiesInputBuilder().build()).get().getResult();
233
234         assertEquals(getEntitiesResult.getEntities().size(), 1);
235         assertTrue(getEntitiesResult.getEntities().get(new EntitiesKey(
236                 new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)), new EntityType(ENTITY_TYPE)))
237                 .getCandidateNodes().contains(new NodeName("member-1")));
238         assertTrue(getEntitiesResult.getEntities().get(new EntitiesKey(
239                         new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)),
240                         new EntityType(ENTITY_TYPE)))
241                 .getOwnerNode().getValue().equals("member-1"));
242
243         final GetEntityOwnerOutput getOwnerResult = service.getEntityOwner(new GetEntityOwnerInputBuilder()
244                         .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
245                         .setType(new EntityType(ENTITY_TYPE))
246                         .build())
247                 .get().getResult();
248
249         assertEquals(getOwnerResult.getOwnerNode().getValue(), "member-1");
250     }
251
252     private static void verifyGetOwnershipState(final DOMEntityOwnershipService service, final DOMEntity entity,
253                                                 final EntityOwnershipState expState) {
254         await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> {
255             final Optional<EntityOwnershipState> state = service.getOwnershipState(entity);
256             assertTrue("getOwnershipState present", state.isPresent());
257             assertEquals("EntityOwnershipState", expState, state.get());
258         });
259     }
260
261     private void verifyEntityCandidateRegistered(final String entityType,
262                                                  final YangInstanceIdentifier entityId,
263                                                  final String candidateName) {
264         await().atMost(Duration.ofSeconds(5))
265                 .untilAsserted(() -> doVerifyEntityCandidateRegistered(entityType, entityId, candidateName));
266     }
267
268     private void doVerifyEntityCandidateRegistered(final String entityType,
269                                                    final YangInstanceIdentifier entityId,
270                                                    final String candidateName)
271             throws ExecutionException, InterruptedException {
272         final Map<DOMEntity, ORSet<String>> entries = getCandidateData();
273         final DOMEntity entity = new DOMEntity(entityType, entityId);
274         assertTrue(entries.containsKey(entity));
275         assertTrue(entries.get(entity).getElements().contains(candidateName));
276     }
277
278     private void verifyEntityCandidateMissing(final String entityType,
279                                               final YangInstanceIdentifier entityId,
280                                               final String candidateName) {
281         await().atMost(Duration.ofSeconds(5))
282                 .untilAsserted(() -> doVerifyEntityCandidateMissing(entityType, entityId, candidateName));
283     }
284
285     private void doVerifyEntityCandidateMissing(final String entityType,
286                                                 final YangInstanceIdentifier entityId,
287                                                 final String candidateName)
288             throws ExecutionException, InterruptedException {
289         final Map<DOMEntity, ORSet<String>> entries = getCandidateData();
290         final DOMEntity entity = new DOMEntity(entityType, entityId);
291         assertTrue(entries.containsKey(entity));
292         assertFalse(entries.get(entity).getElements().contains(candidateName));
293     }
294
295     private Map<DOMEntity, ORSet<String>> getCandidateData() throws ExecutionException, InterruptedException {
296         final CompletionStage<Replicator.GetResponse<ORMap<DOMEntity, ORSet<String>>>> ask =
297                 AskPattern.ask(replicator, replyTo ->
298                                 new Replicator.Get<>(
299                                         CandidateRegistry.KEY,
300                                         Replicator.readLocal(),
301                                         replyTo),
302                         Duration.ofSeconds(5),
303                         typedSystem.scheduler());
304
305         final Replicator.GetResponse<ORMap<DOMEntity, ORSet<String>>> response = ask.toCompletableFuture().get();
306         assertTrue(response instanceof Replicator.GetSuccess);
307
308         final Replicator.GetSuccess<ORMap<DOMEntity, ORSet<String>>> success =
309                 (Replicator.GetSuccess<ORMap<DOMEntity, ORSet<String>>>) response;
310
311         return success.get(CandidateRegistry.KEY).getEntries();
312     }
313
314     private static void verifyEntityOwnershipCandidateRegistration(final DOMEntity entity,
315                                                                    final DOMEntityOwnershipCandidateRegistration reg) {
316         assertNotNull("EntityOwnershipCandidateRegistration null", reg);
317         assertEquals("getInstance", entity, reg.getInstance());
318     }
319 }