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