Add integration test with cluster-singleton
[controller.git] / opendaylight / md-sal / sal-distributed-eos-native / src / test / java / org / opendaylight / controller / cluster / akka / eos / service / ClusterSingletonIntegrationTest.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.cluster.akka.eos.service;
9
10 import static org.awaitility.Awaitility.await;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertTrue;
13
14 import akka.actor.testkit.typed.javadsl.ActorTestKit;
15 import akka.actor.typed.javadsl.Adapter;
16 import akka.cluster.Member;
17 import akka.cluster.MemberStatus;
18 import akka.cluster.typed.Cluster;
19 import akka.testkit.javadsl.TestKit;
20 import com.google.common.util.concurrent.Futures;
21 import com.google.common.util.concurrent.ListenableFuture;
22 import java.time.Duration;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.concurrent.TimeUnit;
26 import org.awaitility.Awaitility;
27 import org.junit.After;
28 import org.junit.Before;
29 import org.junit.Test;
30 import org.opendaylight.controller.cluster.akka.eos.AbstractNativeEosTest;
31 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
32 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
33 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
34 import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
35 import org.opendaylight.mdsal.singleton.dom.impl.DOMClusterSingletonServiceProviderImpl;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 public class ClusterSingletonIntegrationTest extends AbstractNativeEosTest {
40
41     private static final Logger LOG = LoggerFactory.getLogger(ClusterSingletonIntegrationTest.class);
42
43     private MockNativeEntityOwnershipService node1;
44     private MockNativeEntityOwnershipService node2;
45     private MockNativeEntityOwnershipService node3;
46
47     private MockSingletonService singletonNode1;
48     private MockSingletonService singletonNode2;
49     private MockSingletonService singletonNode3;
50
51
52     @Before
53     public void setUp() throws Exception {
54         node1 = startupNativeService(2550, List.of("member-1"), THREE_NODE_SEED_NODES);
55         node2 = startupNativeService(2551, List.of("member-2"), THREE_NODE_SEED_NODES);
56         node3 = startupNativeService(2552, List.of("member-3"), THREE_NODE_SEED_NODES);
57
58         singletonNode1 = new MockSingletonService(node1);
59         singletonNode1.initializeProvider();
60
61         singletonNode2 = new MockSingletonService(node2);
62         singletonNode2.initializeProvider();
63
64         singletonNode3 = new MockSingletonService(node3);
65         singletonNode3.initializeProvider();
66
67         waitUntillNodeReady(node3);
68     }
69
70     @After
71     public void tearDown() {
72         ActorTestKit.shutdown(Adapter.toTyped(node1.getActorSystem()), Duration.ofSeconds(20));
73         ActorTestKit.shutdown(Adapter.toTyped(node2.getActorSystem()), Duration.ofSeconds(20));
74         ActorTestKit.shutdown(Adapter.toTyped(node3.getActorSystem()), Duration.ofSeconds(20));
75     }
76
77     @Test
78     public void testSingletonOwnershipNotDropped() {
79         final MockClusterSingletonService service = new MockClusterSingletonService("member-1", "service-1");
80         singletonNode1.registerClusterSingletonService(service);
81
82         verifyServiceActive(service);
83
84         final MockClusterSingletonService service2 = new MockClusterSingletonService("member-2", "service-1");
85         singletonNode2.registerClusterSingletonService(service2);
86
87         verifyServiceInactive(service2, 2);
88     }
89
90     @Test
91     public void testSingletonOwnershipHandoff() {
92         final MockClusterSingletonService service = new MockClusterSingletonService("member-1", "service-1");
93         final ClusterSingletonServiceRegistration registration =
94                 singletonNode1.registerClusterSingletonService(service);
95
96         verifyServiceActive(service);
97
98         final MockClusterSingletonService service2 = new MockClusterSingletonService("member-2", "service-1");
99         singletonNode2.registerClusterSingletonService(service2);
100
101         verifyServiceInactive(service2, 2);
102
103         registration.close();
104         verifyServiceInactive(service);
105         verifyServiceActive(service2);
106     }
107
108     @Test
109     public void testSingletonOwnershipHandoffOnNodeShutdown() throws Exception {
110         MockClusterSingletonService service2 = new MockClusterSingletonService("member-2", "service-1");
111         ClusterSingletonServiceRegistration registration2 =
112                 singletonNode2.registerClusterSingletonService(service2);
113
114         verifyServiceActive(service2);
115
116         final MockClusterSingletonService service3 = new MockClusterSingletonService("member-3", "service-1");
117         final ClusterSingletonServiceRegistration registration3 =
118                 singletonNode3.registerClusterSingletonService(service3);
119
120         verifyServiceInactive(service3, 2);
121
122         LOG.debug("Shutting down node2");
123         TestKit.shutdownActorSystem(node2.getActorSystem());
124         verifyServiceActive(service3);
125
126         node2 = startupNativeService(2551, List.of("member-1"), THREE_NODE_SEED_NODES);
127         singletonNode2 = new MockSingletonService(node2);
128         singletonNode2.initializeProvider();
129
130         waitUntillNodeReady(node2);
131         service2 = new MockClusterSingletonService("member-2", "service-1");
132         singletonNode2.registerClusterSingletonService(service2);
133
134         verifyServiceActive(service3);
135         verifyServiceInactive(service2, 5);
136     }
137
138     private void waitUntillNodeReady(MockNativeEntityOwnershipService node) {
139         // need to wait until all nodes are ready
140         final Cluster cluster = Cluster.get(Adapter.toTyped(node.getActorSystem()));
141         Awaitility.await().atMost(Duration.ofSeconds(20)).until(() -> {
142             final List<Member> members = new ArrayList<>();
143             cluster.state().getMembers().forEach(members::add);
144             if (members.size() != 3) {
145                 return false;
146             }
147
148             for (final Member member : members) {
149                 if (!member.status().equals(MemberStatus.up())) {
150                     return false;
151                 }
152             }
153
154             return true;
155         });
156     }
157
158     private static void verifyServiceActive(MockClusterSingletonService service) {
159         await().untilAsserted(() -> assertTrue(service.isActivated()));
160     }
161
162     private static void verifyServiceActive(MockClusterSingletonService service, long delay) {
163         await().pollDelay(delay, TimeUnit.SECONDS).untilAsserted(() -> assertTrue(service.isActivated()));
164     }
165
166     private static void verifyServiceInactive(MockClusterSingletonService service) {
167         await().untilAsserted(() -> assertFalse(service.isActivated()));
168     }
169
170     private static void verifyServiceInactive(MockClusterSingletonService service, long delay) {
171         await().pollDelay(delay, TimeUnit.SECONDS).untilAsserted(() -> assertFalse(service.isActivated()));
172     }
173
174     private static class MockClusterSingletonService implements ClusterSingletonService {
175
176         private final String member;
177         private final ServiceGroupIdentifier identifier;
178         private boolean activated = false;
179
180         MockClusterSingletonService(String member, String identifier) {
181             this.member = member;
182             this.identifier = ServiceGroupIdentifier.create(identifier);
183         }
184
185         @Override
186         public void instantiateServiceInstance() {
187             LOG.debug("{} : Activating service: {}", member, identifier);
188             activated = true;
189         }
190
191         @Override
192         public ListenableFuture<? extends Object> closeServiceInstance() {
193             LOG.debug("{} : Closing service: {}", member, identifier);
194             activated = false;
195             return Futures.immediateFuture(null);
196         }
197
198         @Override
199         public ServiceGroupIdentifier getIdentifier() {
200             return identifier;
201         }
202
203         public boolean isActivated() {
204             return activated;
205         }
206     }
207
208     private static class MockSingletonService extends DOMClusterSingletonServiceProviderImpl {
209         MockSingletonService(DOMEntityOwnershipService entityOwnershipService) {
210             super(entityOwnershipService);
211         }
212     }
213 }