522eb55dc7b25003b2eac34dbc4f613b2ad1f965
[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.GetEntityInputBuilder;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerInputBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.get.entities.output.EntitiesKey;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
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;
60
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");
65
66     private ActorSystem system;
67     private akka.actor.typed.ActorSystem<Void> typedSystem;
68     private AkkaEntityOwnershipService service;
69     private ActorRef<Replicator.Command> replicator;
70
71     @Before
72     public void setUp() throws Exception {
73         system = ActorSystem.create("ClusterSystem", ConfigFactory.load());
74         typedSystem = Adapter.toTyped(system);
75         replicator = DistributedData.get(typedSystem).replicator();
76
77         service = new AkkaEntityOwnershipService(system, CODEC_CONTEXT);
78     }
79
80     @After
81     public void tearDown() throws InterruptedException, ExecutionException {
82         service.close();
83         ActorTestKit.shutdown(Adapter.toTyped(system), Duration.ofSeconds(20));
84     }
85
86     @Test
87     public void testRegisterCandidate() throws Exception {
88         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
89         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
90
91         final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
92
93         verifyEntityOwnershipCandidateRegistration(entity, reg);
94         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
95
96         try {
97             service.registerCandidate(entity);
98             fail("Expected CandidateAlreadyRegisteredException");
99         } catch (final CandidateAlreadyRegisteredException e) {
100             // expected
101             assertEquals("getEntity", entity, e.getEntity());
102         }
103
104         final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE2, entityId);
105         final DOMEntityOwnershipCandidateRegistration reg2 = service.registerCandidate(entity2);
106
107         verifyEntityOwnershipCandidateRegistration(entity2, reg2);
108         verifyEntityCandidateRegistered(ENTITY_TYPE2, entityId, "member-1");
109     }
110
111     @Test
112     public void testUnregisterCandidate() throws Exception {
113         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
114         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
115
116         final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
117
118         verifyEntityOwnershipCandidateRegistration(entity, reg);
119         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
120
121         reg.close();
122         verifyEntityCandidateMissing(ENTITY_TYPE, entityId, "member-1");
123
124         service.registerCandidate(entity);
125         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
126     }
127
128     @Test
129     public void testListenerRegistration() throws Exception {
130
131         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(QNAME);
132         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
133         final MockEntityOwnershipListener listener = new MockEntityOwnershipListener("member-1");
134
135         final DOMEntityOwnershipListenerRegistration reg = service.registerListener(entity.getType(), listener);
136
137         assertNotNull("EntityOwnershipListenerRegistration null", reg);
138         assertEquals("getEntityType", entity.getType(), reg.getEntityType());
139         assertEquals("getInstance", listener, reg.getInstance());
140
141         final DOMEntityOwnershipCandidateRegistration candidate = service.registerCandidate(entity);
142
143         verifyListenerState(listener, entity, true, true, false);
144         final int changes = listener.getChanges().size();
145
146         reg.close();
147         candidate.close();
148
149         verifyEntityCandidateMissing(ENTITY_TYPE, entityId, "member-1");
150
151         service.registerCandidate(entity);
152         // check listener not called when listener registration closed
153         await().pollDelay(Durations.TWO_SECONDS).until(() -> listener.getChanges().size() == changes);
154     }
155
156     @Test
157     public void testGetOwnershipState() throws Exception {
158         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, "one");
159
160         final DOMEntityOwnershipCandidateRegistration registration = service.registerCandidate(entity);
161         verifyGetOwnershipState(service, entity, EntityOwnershipState.IS_OWNER);
162
163         final RunningContext runningContext = service.getRunningContext();
164         registerCandidates(runningContext.getCandidateRegistry(), entity, "member-2");
165
166         final ActorRef<OwnerSupervisorCommand> ownerSupervisor = runningContext.getOwnerSupervisor();
167         reachableMember(ownerSupervisor, "member-2", DEFAULT_DATACENTER);
168         unreachableMember(ownerSupervisor, "member-1", DEFAULT_DATACENTER);
169         verifyGetOwnershipState(service, entity, EntityOwnershipState.OWNED_BY_OTHER);
170
171         final DOMEntity entity2 = new DOMEntity(ENTITY_TYPE, "two");
172         final Optional<EntityOwnershipState> state = service.getOwnershipState(entity2);
173         assertFalse(state.isPresent());
174
175         unreachableMember(ownerSupervisor, "member-2", DEFAULT_DATACENTER);
176         verifyGetOwnershipState(service, entity, EntityOwnershipState.NO_OWNER);
177     }
178
179     @Test
180     public void testIsCandidateRegistered() throws Exception {
181         final DOMEntity test = new DOMEntity("test-type", "test");
182
183         assertFalse(service.isCandidateRegistered(test));
184
185         service.registerCandidate(test);
186
187         assertTrue(service.isCandidateRegistered(test));
188     }
189
190     @Test
191     public void testEntityRetrievalWithYiid() throws Exception {
192         final YangInstanceIdentifier entityId = YangInstanceIdentifier.of(new NodeIdentifier(NetworkTopology.QNAME),
193                 new NodeIdentifier(Topology.QNAME),
194                 NodeIdentifierWithPredicates.of(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), "test"),
195                 new NodeIdentifier(Node.QNAME),
196                 NodeIdentifierWithPredicates.of(Node.QNAME, QName.create(Node.QNAME, "node-id"), "test://test-node"));
197
198         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
199
200         final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(entity);
201
202         verifyEntityOwnershipCandidateRegistration(entity, reg);
203         verifyEntityCandidateRegistered(ENTITY_TYPE, entityId, "member-1");
204
205         var getEntityResult = service.getEntity(new GetEntityInputBuilder()
206             .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
207             .setType(new EntityType(ENTITY_TYPE))
208             .build())
209             .get();
210
211         assertEquals(getEntityResult.getResult().getOwnerNode().getValue(), "member-1");
212         assertEquals(getEntityResult.getResult().getCandidateNodes().get(0).getValue(), "member-1");
213
214         // we should not be able to retrieve the entity when using string
215         final String entityPathEncoded =
216                 "/network-topology:network-topology/topology[topology-id='test']/node[node-id='test://test-node']";
217
218         getEntityResult = service.getEntity(new GetEntityInputBuilder()
219                 .setName(new EntityName(entityPathEncoded))
220                 .setType(new EntityType(ENTITY_TYPE))
221                 .build())
222                 .get();
223
224         assertNull(getEntityResult.getResult().getOwnerNode());
225         assertTrue(getEntityResult.getResult().getCandidateNodes().isEmpty());
226
227         final var getEntitiesResult = service.getEntities(new GetEntitiesInputBuilder().build()).get().getResult();
228
229         assertEquals(getEntitiesResult.getEntities().size(), 1);
230         assertTrue(getEntitiesResult.getEntities().get(new EntitiesKey(
231                 new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)), new EntityType(ENTITY_TYPE)))
232                 .getCandidateNodes().contains(new NodeName("member-1")));
233         assertTrue(getEntitiesResult.getEntities().get(new EntitiesKey(
234                         new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)),
235                         new EntityType(ENTITY_TYPE)))
236                 .getOwnerNode().getValue().equals("member-1"));
237
238         final var getOwnerResult = service.getEntityOwner(new GetEntityOwnerInputBuilder()
239             .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
240             .setType(new EntityType(ENTITY_TYPE))
241             .build()).get().getResult();
242
243         assertEquals(getOwnerResult.getOwnerNode().getValue(), "member-1");
244     }
245
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));
250         });
251     }
252
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));
258     }
259
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));
268     }
269
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));
275     }
276
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));
285     }
286
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(),
293                                         replyTo),
294                         Duration.ofSeconds(5),
295                         typedSystem.scheduler());
296
297         final Replicator.GetResponse<ORMap<DOMEntity, ORSet<String>>> response = ask.toCompletableFuture().get();
298         assertTrue(response instanceof Replicator.GetSuccess);
299
300         final Replicator.GetSuccess<ORMap<DOMEntity, ORSet<String>>> success =
301                 (Replicator.GetSuccess<ORMap<DOMEntity, ORSet<String>>>) response;
302
303         return success.get(CandidateRegistry.KEY).getEntries();
304     }
305
306     private static void verifyEntityOwnershipCandidateRegistration(final DOMEntity entity,
307                                                                    final DOMEntityOwnershipCandidateRegistration reg) {
308         assertNotNull("EntityOwnershipCandidateRegistration null", reg);
309         assertEquals("getInstance", entity, reg.getInstance());
310     }
311 }