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