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