BUG-6975: Integrate Programming with Cluster Singleton Service
[bgpcep.git] / programming / impl / src / test / java / org / opendaylight / bgpcep / programming / impl / ProgrammingServiceImplTest.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.bgpcep.programming.impl;
9
10 import static org.hamcrest.CoreMatchers.containsString;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertThat;
14 import static org.junit.Assert.assertTrue;
15 import static org.junit.Assert.fail;
16 import static org.mockito.Matchers.any;
17 import static org.mockito.Mockito.doAnswer;
18 import static org.mockito.Mockito.doNothing;
19 import static org.mockito.Mockito.doReturn;
20 import static org.mockito.Mockito.mock;
21
22 import com.google.common.collect.Lists;
23 import com.google.common.util.concurrent.ListenableFuture;
24 import io.netty.util.HashedWheelTimer;
25 import io.netty.util.Timer;
26 import java.math.BigInteger;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.concurrent.ExecutionException;
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Test;
33 import org.mockito.Mock;
34 import org.mockito.Mockito;
35 import org.mockito.MockitoAnnotations;
36 import org.opendaylight.bgpcep.programming.NanotimeUtil;
37 import org.opendaylight.bgpcep.programming.spi.Instruction;
38 import org.opendaylight.bgpcep.programming.spi.SchedulerException;
39 import org.opendaylight.controller.md.sal.binding.test.AbstractDataBrokerTest;
40 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
41 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
42 import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
43 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
44 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
45 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.CancelInstructionInput;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.CancelInstructionInputBuilder;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.CleanInstructionsInput;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.CleanInstructionsInputBuilder;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.CleanInstructionsOutput;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.InstructionId;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.InstructionStatus;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.InstructionsQueue;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.InstructionsQueueKey;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.Nanotime;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.ProgrammingService;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.SubmitInstructionInput;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.queue.InstructionKey;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.status.changed.Details;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.status.changed.DetailsBuilder;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.programming.rev131030.PcepUpdateTunnelInput;
62 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
63 import org.opendaylight.yangtools.yang.common.RpcResult;
64
65 public class ProgrammingServiceImplTest extends AbstractDataBrokerTest {
66
67     private static final int INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS = 3;
68     private static final String INSTRUCTIONS_QUEUE_KEY = "test-instraction-queue";
69     private final Timer timer = new HashedWheelTimer();
70     private MockedExecutorWrapper mockedExecutorWrapper;
71     private MockedNotificationServiceWrapper mockedNotificationServiceWrapper;
72     private ProgrammingServiceImpl testedProgrammingService;
73     @Mock
74     private ClusterSingletonServiceProvider cssp;
75     @Mock
76     private ClusterSingletonServiceRegistration singletonServiceRegistration;
77     @Mock
78     private RpcProviderRegistry rpcRegistry;
79     @Mock
80     private RoutedRpcRegistration<ProgrammingService> registration;
81     private ClusterSingletonService singletonService;
82
83     @Before
84     public void setUp() throws Exception {
85         MockitoAnnotations.initMocks(this);
86         doAnswer(invocationOnMock -> {
87             this.singletonService = (ClusterSingletonService) invocationOnMock.getArguments()[0];
88             return this.singletonServiceRegistration;
89         }).when(this.cssp).registerClusterSingletonService(any(ClusterSingletonService.class));
90
91         doAnswer(invocationOnMock -> {
92             this.singletonService.closeServiceInstance();
93             return null;
94         }).when(this.singletonServiceRegistration).close();
95         doReturn(this.registration).when(this.rpcRegistry).addRpcImplementation(Mockito.any(),
96             Mockito.any(ProgrammingService.class));
97         doNothing().when(this.registration).close();
98         this.mockedExecutorWrapper = new MockedExecutorWrapper();
99         this.mockedNotificationServiceWrapper = new MockedNotificationServiceWrapper();
100
101         this.testedProgrammingService = new ProgrammingServiceImpl(getDataBroker(),
102             this.mockedNotificationServiceWrapper.getMockedNotificationService(),
103             this.mockedExecutorWrapper.getMockedExecutor(), this.rpcRegistry, this.cssp, this.timer,
104             INSTRUCTIONS_QUEUE_KEY);
105         this.singletonService.instantiateServiceInstance();
106     }
107
108     @After
109     public void tearDown() throws Exception {
110         this.singletonService.closeServiceInstance();
111         this.testedProgrammingService.close();
112     }
113
114     @Test
115     public void testScheduleInstruction() throws Exception {
116         final SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit");
117         this.testedProgrammingService.scheduleInstruction(mockedSubmit);
118
119         assertTrue(assertInstructionExists(mockedSubmit.getId()));
120
121         // assert Schedule to executor
122         this.mockedExecutorWrapper.assertSubmittedTasksSize(1);
123
124         // assert Notification
125         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
126         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(0, mockedSubmit.getId(), InstructionStatus.Scheduled);
127     }
128
129     @Test
130     public void testScheduleDependingInstruction() throws Exception {
131         this.testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit1"));
132         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
133         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
134
135         this.mockedExecutorWrapper.assertSubmittedTasksSize(2);
136
137         // First is in state scheduled, so second could not be scheduled yet
138         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
139     }
140
141     @Test
142     public void testScheduleDependingInstructionToFail() throws Exception {
143         try {
144             this.testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit", "dep1"));
145         } catch (final SchedulerException e) {
146             assertThat(e.getMessage(), containsString("Unknown dependency ID"));
147             this.mockedNotificationServiceWrapper.assertNotificationsCount(0);
148             return;
149         }
150         fail("Instruction schedule should fail on unresolved dependencies");
151     }
152
153     @Test
154     public void testCancelInstruction() throws Exception {
155         final SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit");
156         this.testedProgrammingService.scheduleInstruction(mockedSubmit);
157
158         assertTrue(assertInstructionExists(mockedSubmit.getId()));
159
160         final CancelInstructionInput mockedCancel = getCancelInstruction("mockedSubmit");
161         this.testedProgrammingService.cancelInstruction(mockedCancel);
162
163         assertTrue(assertInstructionExists(mockedSubmit.getId()));
164
165         this.mockedExecutorWrapper.assertSubmittedTasksSize(2);
166
167         this.mockedNotificationServiceWrapper.assertNotificationsCount(2);
168         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit.getId(), InstructionStatus.Cancelled);
169     }
170
171     @Test
172     public void testCancelDependantInstruction() throws Exception {
173         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
174         this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
175         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
176         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
177         final SubmitInstructionInput mockedSubmit3 = getMockedSubmitInstructionInput("mockedSubmit3", "mockedSubmit1", "mockedSubmit2");
178         this.testedProgrammingService.scheduleInstruction(mockedSubmit3);
179
180         this.testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1"));
181
182         this.mockedNotificationServiceWrapper.assertNotificationsCount(1 /*First Scheduled*/+ 3 /*First and all dependencies cancelled*/);
183         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(0, mockedSubmit1.getId(), InstructionStatus.Scheduled);
184         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Cancelled);
185         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit2.getId(), InstructionStatus.Cancelled);
186         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit3.getId(), InstructionStatus.Cancelled);
187
188         assertTrue(assertInstructionExists(mockedSubmit1.getId()));
189         assertTrue(assertInstructionExists(mockedSubmit2.getId()));
190         assertTrue(assertInstructionExists(mockedSubmit3.getId()));
191     }
192
193     @Test
194     public void testCleanInstructions() throws Exception {
195         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
196         this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
197         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
198         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
199
200         final CleanInstructionsInputBuilder cleanInstructionsInputBuilder = new CleanInstructionsInputBuilder();
201         final CleanInstructionsInput cleanInstructionsInput = cleanInstructionsInputBuilder.setId(
202                 Lists.newArrayList(mockedSubmit1.getId(), mockedSubmit2.getId())).build();
203
204         ListenableFuture<RpcResult<CleanInstructionsOutput>> cleanedInstructionOutput = this.testedProgrammingService.cleanInstructions(cleanInstructionsInput);
205
206         assertCleanInstructionOutput(cleanedInstructionOutput, 2);
207
208         this.testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1"));
209
210         cleanedInstructionOutput = this.testedProgrammingService.cleanInstructions(cleanInstructionsInput);
211         assertCleanInstructionOutput(cleanedInstructionOutput, 0);
212
213         assertFalse(assertInstructionExists(mockedSubmit1.getId()));
214         assertFalse(assertInstructionExists(mockedSubmit2.getId()));
215     }
216
217     private void assertCleanInstructionOutput(final ListenableFuture<RpcResult<CleanInstructionsOutput>> cleanedInstructionOutput,
218             final int unflushedCount) throws InterruptedException, java.util.concurrent.ExecutionException {
219         if (unflushedCount == 0) {
220             final List<InstructionId> unflushed = cleanedInstructionOutput.get().getResult().getUnflushed();
221             assertTrue(unflushed == null || unflushed.isEmpty());
222         } else {
223             assertEquals(unflushedCount, cleanedInstructionOutput.get().getResult().getUnflushed().size());
224         }
225         assertEquals(0, cleanedInstructionOutput.get().getErrors().size());
226     }
227
228     @Test
229     public void testCloseProgrammingService() throws Exception {
230         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
231         this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
232         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
233         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
234
235         this.testedProgrammingService.close();
236
237         this.mockedNotificationServiceWrapper.assertNotificationsCount(1/* First scheduled */+ 2/* Both cancelled at close */);
238     }
239
240     @Test(timeout = 30 * 1000)
241     public void testTimeoutWhileScheduledTransaction() throws Exception {
242         final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
243         final Nanotime current = NanotimeUtil.currentTime();
244         final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
245
246         final Optional<Nanotime> deadline = Optional.of(deadlineNano);
247         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
248         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
249
250         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
251
252         future.get();
253
254         Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000);
255
256         this.mockedNotificationServiceWrapper.assertNotificationsCount(2);
257         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Cancelled);
258     }
259
260     @Test(timeout = 30 * 1000)
261     public void testTimeoutWhileSuccessfulTransaction() throws Exception {
262         final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
263         final Nanotime current = NanotimeUtil.currentTime();
264         final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
265
266         final Optional<Nanotime> deadline = Optional.of(deadlineNano);
267         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
268         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
269
270         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
271
272         final Instruction i = future.get();
273         i.checkedExecutionStart();
274         i.executionCompleted(InstructionStatus.Successful, getDetails());
275
276         Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000);
277
278         this.mockedNotificationServiceWrapper.assertNotificationsCount(3);
279         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Executing);
280         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), InstructionStatus.Successful);
281         // Timeout in success should not do anything
282     }
283
284     @Test(timeout = 30 * 1000)
285     public void testTimeoutWhileExecutingWithDependenciesTransaction() throws Exception {
286         final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
287         final Nanotime current = NanotimeUtil.currentTime();
288         final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
289
290         final Optional<Nanotime> deadline = Optional.of(deadlineNano);
291         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
292         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
293
294         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
295         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
296
297         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
298
299         final Instruction i = future.get();
300         i.checkedExecutionStart();
301
302         Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000);
303
304         this.mockedNotificationServiceWrapper.assertNotificationsCount(4);
305         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Executing);
306         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), InstructionStatus.Unknown);
307         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(), InstructionStatus.Cancelled);
308     }
309
310     // TODO test deadline with state Queued
311
312     @Test
313     public void testSuccessExecutingWithDependenciesTransaction() throws Exception {
314         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
315         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
316
317         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
318         final ListenableFuture<Instruction> future2 = this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
319
320         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
321
322         Instruction i = future.get();
323         i.checkedExecutionStart();
324         i.executionCompleted(InstructionStatus.Successful, getDetails());
325
326         this.mockedNotificationServiceWrapper.assertNotificationsCount(4);
327         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Executing);
328         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), InstructionStatus.Successful);
329         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(), InstructionStatus.Scheduled);
330
331         i = future2.get();
332         i.checkedExecutionStart();
333         i.executionCompleted(InstructionStatus.Successful, getDetails());
334
335         this.mockedNotificationServiceWrapper.assertNotificationsCount(6);
336         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(4, mockedSubmit2.getId(), InstructionStatus.Executing);
337         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(5, mockedSubmit2.getId(), InstructionStatus.Successful);
338     }
339
340     private Details getDetails() {
341         return new DetailsBuilder().build();
342     }
343
344     private SubmitInstructionInput getMockedSubmitInstructionInput(final String id, final String... dependencyIds) {
345         return getMockedSubmitInstructionInput(id, Optional.empty(), dependencyIds);
346     }
347
348     private SubmitInstructionInput getMockedSubmitInstructionInput(final String id, final Optional<Nanotime> deadline, final String... dependencyIds) {
349         final SubmitInstructionInput mockedSubmitInstruction = mock(SubmitInstructionInput.class);
350
351         doReturn(PcepUpdateTunnelInput.class).when(mockedSubmitInstruction).getImplementedInterface();
352         final List<InstructionId> dependencies = Lists.newArrayList();
353         for (final String dependencyId : dependencyIds) {
354             dependencies.add(getInstructionId(dependencyId));
355         }
356
357         doReturn(dependencies).when(mockedSubmitInstruction).getPreconditions();
358         doReturn(getInstructionId(id)).when(mockedSubmitInstruction).getId();
359         doReturn(deadline.isPresent() ? deadline.get() : new Nanotime(BigInteger.valueOf(Long.MAX_VALUE))).when(mockedSubmitInstruction).getDeadline();
360         return mockedSubmitInstruction;
361     }
362
363     private CancelInstructionInput getCancelInstruction(final String instructionId) {
364         final CancelInstructionInputBuilder builder = new CancelInstructionInputBuilder();
365         builder.setId(getInstructionId(instructionId));
366         return builder.build();
367     }
368
369     private InstructionId getInstructionId(final String id) {
370         return new InstructionId(id);
371     }
372
373     private boolean assertInstructionExists(final InstructionId id) {
374         try {
375             return getDataBroker().newReadOnlyTransaction().read(LogicalDatastoreType.OPERATIONAL,
376                 InstanceIdentifier.builder(InstructionsQueue.class, new InstructionsQueueKey(INSTRUCTIONS_QUEUE_KEY))
377                     .build().child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.queue.Instruction.class,
378                     new InstructionKey(id))).get().isPresent();
379         } catch (InterruptedException | ExecutionException e) {
380             return false;
381         }
382     }
383 }