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