Add an actor for entity rpc execution.
[controller.git] / opendaylight / md-sal / eos-dom-akka / src / test / java / org / opendaylight / controller / eos / akka / EntityRpcHandlerTest.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.assertTrue;
13
14 import akka.actor.ActorSystem;
15 import akka.actor.testkit.typed.javadsl.ActorTestKit;
16 import akka.actor.typed.javadsl.Adapter;
17 import akka.cluster.Member;
18 import akka.cluster.MemberStatus;
19 import akka.cluster.typed.Cluster;
20 import java.time.Duration;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.ExecutionException;
24 import org.awaitility.Awaitility;
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
29 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityName;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityType;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesInputBuilder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesOutput;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityInputBuilder;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOutput;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerInputBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerOutput;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.get.entities.output.EntitiesKey;
40 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
41 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
43 import org.opendaylight.yangtools.yang.common.QName;
44 import org.opendaylight.yangtools.yang.common.RpcResult;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
48
49 public class EntityRpcHandlerTest extends AbstractNativeEosTest {
50     static final String ENTITY_TYPE = "test";
51
52     private ActorSystem system1;
53     private ActorSystem system2;
54
55     private AkkaEntityOwnershipService service1;
56     private AkkaEntityOwnershipService service2;
57
58     @Before
59     public void setUp() throws Exception {
60         system1 = startupActorSystem(2550, List.of("member-1"), TWO_NODE_SEED_NODES);
61         system2 = startupActorSystem(2551, List.of("member-2"), TWO_NODE_SEED_NODES, "dc-backup");
62
63         service1 = new AkkaEntityOwnershipService(system1, CODEC_CONTEXT);
64         service2 = new AkkaEntityOwnershipService(system2, CODEC_CONTEXT);
65
66         // need to wait until all nodes are ready
67         final Cluster cluster = Cluster.get(Adapter.toTyped(system2));
68         Awaitility.await().atMost(Duration.ofSeconds(20)).until(() -> {
69             final List<Member> members = new ArrayList<>();
70             cluster.state().getMembers().forEach(members::add);
71             if (members.size() != 2) {
72                 return false;
73             }
74
75             for (final Member member : members) {
76                 if (!member.status().equals(MemberStatus.up())) {
77                     return false;
78                 }
79             }
80
81             return true;
82         });
83     }
84
85     @After
86     public void tearDown() throws InterruptedException, ExecutionException {
87         service1.close();
88         service2.close();
89         ActorTestKit.shutdown(Adapter.toTyped(system1), Duration.ofSeconds(20));
90         ActorTestKit.shutdown(Adapter.toTyped(system2), Duration.ofSeconds(20));
91     }
92
93     /*
94      * Tests entity rpcs handled both by the owner supervisor(service1) and with an idle supervisor(falling
95      * back to distributed-data in an inactive datacenter). This covers both the available cases, datacenters and case
96      * in which the node with active akka-singleton is shutdown and another one takes over.
97      */
98     @Test
99     public void testEntityRetrievalWithUnavailableSupervisor() throws Exception {
100         final YangInstanceIdentifier entityId = YangInstanceIdentifier.create(new NodeIdentifier(NetworkTopology.QNAME),
101                 new NodeIdentifier(Topology.QNAME),
102                 NodeIdentifierWithPredicates.of(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), "test"),
103                 new NodeIdentifier(Node.QNAME),
104                 NodeIdentifierWithPredicates.of(Node.QNAME, QName.create(Node.QNAME, "node-id"), "test://test-node"));
105
106         final DOMEntity entity = new DOMEntity(ENTITY_TYPE, entityId);
107
108         final DOMEntityOwnershipCandidateRegistration reg = service1.registerCandidate(entity);
109
110         await().untilAsserted(() -> {
111             final RpcResult<GetEntityOutput> getEntityResult = service1.getEntity(new GetEntityInputBuilder()
112                             .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
113                             .setType(new EntityType(ENTITY_TYPE))
114                             .build())
115                     .get();
116
117             assertEquals(getEntityResult.getResult().getOwnerNode().getValue(), "member-1");
118             assertEquals(getEntityResult.getResult().getCandidateNodes().get(0).getValue(), "member-1");
119         });
120
121         // keep this under ask timeout to make sure the singleton actor in the inactive datacenter responds with failure
122         // immediately, so that the rpc actor retries with distributed-data asap
123         await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {
124             final GetEntitiesOutput getEntitiesResult =
125                     service2.getEntities(new GetEntitiesInputBuilder().build()).get().getResult();
126
127             assertEquals(getEntitiesResult.getEntities().size(), 1);
128             assertTrue(getEntitiesResult.getEntities().get(new EntitiesKey(
129                             new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)),
130                             new EntityType(ENTITY_TYPE)))
131                     .getCandidateNodes().contains(new NodeName("member-1")));
132             assertTrue(getEntitiesResult.getEntities().get(new EntitiesKey(
133                             new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)),
134                             new EntityType(ENTITY_TYPE)))
135                     .getOwnerNode().getValue().equals("member-1"));
136         });
137
138         await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {
139             final GetEntityOutput getEntityResult = service2.getEntity(new GetEntityInputBuilder()
140                             .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
141                             .setType(new EntityType(ENTITY_TYPE))
142                             .build())
143                     .get().getResult();
144
145             assertEquals(getEntityResult.getOwnerNode().getValue(), "member-1");
146             assertEquals(getEntityResult.getCandidateNodes().get(0).getValue(), "member-1");
147         });
148
149         await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {
150             final GetEntityOwnerOutput getOwnerResult = service2.getEntityOwner(new GetEntityOwnerInputBuilder()
151                             .setName(new EntityName(CODEC_CONTEXT.fromYangInstanceIdentifier(entityId)))
152                             .setType(new EntityType(ENTITY_TYPE))
153                             .build())
154                     .get().getResult();
155
156             assertEquals(getOwnerResult.getOwnerNode().getValue(), "member-1");
157         });
158
159     }
160 }