65efb3533179c86d0ddf215745f8d253e7207b7e
[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.assertThat;
13 import static org.junit.Assert.assertTrue;
14 import static org.junit.Assert.fail;
15 import static org.mockito.Matchers.any;
16 import static org.mockito.Mockito.doAnswer;
17 import static org.mockito.Mockito.doNothing;
18 import static org.mockito.Mockito.doReturn;
19 import static org.mockito.Mockito.mock;
20 import static org.mockito.MockitoAnnotations.initMocks;
21 import static org.opendaylight.protocol.util.CheckUtil.checkNotPresentOperational;
22 import static org.opendaylight.protocol.util.CheckUtil.checkPresentOperational;
23
24 import com.google.common.collect.Lists;
25 import com.google.common.util.concurrent.ListenableFuture;
26 import io.netty.util.HashedWheelTimer;
27 import io.netty.util.Timer;
28 import java.math.BigInteger;
29 import java.util.List;
30 import java.util.Optional;
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.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.binding.KeyedInstanceIdentifier;
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         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         checkPresentOperational(getDataBroker(), buildInstructionIID(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(),
128             InstructionStatus.Scheduled);
129     }
130
131     @Test
132     public void testScheduleDependingInstruction() throws Exception {
133         this.testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit1"));
134         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2",
135             "mockedSubmit1");
136         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
137
138         this.mockedExecutorWrapper.assertSubmittedTasksSize(2);
139
140         // First is in state scheduled, so second could not be scheduled yet
141         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
142     }
143
144     @Test
145     public void testScheduleDependingInstructionToFail() throws Exception {
146         try {
147             this.testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit",
148                 "dep1"));
149         } catch (final SchedulerException e) {
150             assertThat(e.getMessage(), containsString("Unknown dependency ID"));
151             this.mockedNotificationServiceWrapper.assertNotificationsCount(0);
152             return;
153         }
154         fail("Instruction schedule should fail on unresolved dependencies");
155     }
156
157     @Test
158     public void testCancelInstruction() throws Exception {
159         final SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit");
160         this.testedProgrammingService.scheduleInstruction(mockedSubmit);
161         checkPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit.getId()));
162
163         final CancelInstructionInput mockedCancel = getCancelInstruction("mockedSubmit");
164         this.testedProgrammingService.cancelInstruction(mockedCancel);
165
166         checkPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit.getId()));
167         this.mockedExecutorWrapper.assertSubmittedTasksSize(2);
168
169         this.mockedNotificationServiceWrapper.assertNotificationsCount(2);
170         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit.getId(),
171             InstructionStatus.Cancelled);
172     }
173
174     @Test
175     public void testCancelDependantInstruction() throws Exception {
176         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
177         this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
178         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2",
179             "mockedSubmit1");
180         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
181         final SubmitInstructionInput mockedSubmit3 = getMockedSubmitInstructionInput("mockedSubmit3",
182             "mockedSubmit1", "mockedSubmit2");
183         this.testedProgrammingService.scheduleInstruction(mockedSubmit3);
184
185         this.testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1"));
186
187         this.mockedNotificationServiceWrapper.assertNotificationsCount(1 /*First Scheduled*/+ 3 /*First and all dependencies cancelled*/);
188         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(0, mockedSubmit1.getId(),
189             InstructionStatus.Scheduled);
190         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(),
191             InstructionStatus.Cancelled);
192         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit2.getId(),
193             InstructionStatus.Cancelled);
194         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit3.getId(),
195             InstructionStatus.Cancelled);
196
197         checkPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit1.getId()));
198         checkPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit2.getId()));
199         checkPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit3.getId()));
200     }
201
202     @Test
203     public void testCleanInstructions() throws Exception {
204         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
205         this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
206         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2",
207             "mockedSubmit1");
208         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
209
210         final CleanInstructionsInputBuilder cleanInstructionsInputBuilder = new CleanInstructionsInputBuilder();
211         final CleanInstructionsInput cleanInstructionsInput = cleanInstructionsInputBuilder.setId(
212                 Lists.newArrayList(mockedSubmit1.getId(), mockedSubmit2.getId())).build();
213
214         ListenableFuture<RpcResult<CleanInstructionsOutput>> cleanedInstructionOutput = this.testedProgrammingService
215             .cleanInstructions(cleanInstructionsInput);
216
217         assertCleanInstructionOutput(cleanedInstructionOutput, 2);
218
219         this.testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1"));
220
221         cleanedInstructionOutput = this.testedProgrammingService.cleanInstructions(cleanInstructionsInput);
222         assertCleanInstructionOutput(cleanedInstructionOutput, 0);
223
224         checkNotPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit1.getId()));
225         checkNotPresentOperational(getDataBroker(), buildInstructionIID(mockedSubmit2.getId()));
226     }
227
228     private void assertCleanInstructionOutput(final ListenableFuture<RpcResult<CleanInstructionsOutput>>
229         cleanedInstructionOutput, final int unflushedCount) throws InterruptedException,
230         java.util.concurrent.ExecutionException {
231         if (unflushedCount == 0) {
232             final List<InstructionId> unflushed = cleanedInstructionOutput.get().getResult().getUnflushed();
233             assertTrue(unflushed == null || unflushed.isEmpty());
234         } else {
235             assertEquals(unflushedCount, cleanedInstructionOutput.get().getResult().getUnflushed().size());
236         }
237         assertEquals(0, cleanedInstructionOutput.get().getErrors().size());
238     }
239
240     @Test
241     public void testCloseProgrammingService() throws Exception {
242         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
243         this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
244         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2",
245             "mockedSubmit1");
246         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
247
248         this.testedProgrammingService.close();
249
250         this.mockedNotificationServiceWrapper
251             .assertNotificationsCount(1/* First scheduled */+ 2/* Both cancelled at close */);
252     }
253
254     @Test(timeout = 30 * 1000)
255     public void testTimeoutWhileScheduledTransaction() throws Exception {
256         final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 *
257             INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
258         final Nanotime current = NanotimeUtil.currentTime();
259         final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
260
261         final Optional<Nanotime> deadline = Optional.of(deadlineNano);
262         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
263         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
264
265         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
266
267         future.get();
268
269         this.mockedNotificationServiceWrapper.assertNotificationsCount(2);
270         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(),
271             InstructionStatus.Cancelled);
272     }
273
274     @Test(timeout = 30 * 1000)
275     public void testTimeoutWhileSuccessfulTransaction() throws Exception {
276         final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 *
277             INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
278         final Nanotime current = NanotimeUtil.currentTime();
279         final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
280
281         final Optional<Nanotime> deadline = Optional.of(deadlineNano);
282         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
283         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
284
285         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
286
287         final Instruction i = future.get();
288         i.checkedExecutionStart();
289         i.executionCompleted(InstructionStatus.Successful, getDetails());
290
291         this.mockedNotificationServiceWrapper.assertNotificationsCount(3);
292         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(),
293             InstructionStatus.Executing);
294         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(),
295             InstructionStatus.Successful);
296         // Timeout in success should not do anything
297     }
298
299     @Test(timeout = 30 * 1000)
300     public void testTimeoutWhileExecutingWithDependenciesTransaction() throws Exception {
301         final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 *
302             INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
303         final Nanotime current = NanotimeUtil.currentTime();
304         final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
305
306         final Optional<Nanotime> deadline = Optional.of(deadlineNano);
307         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
308         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
309
310         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2",
311             "mockedSubmit1");
312         this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
313
314         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
315
316         final Instruction i = future.get();
317         i.checkedExecutionStart();
318
319         this.mockedNotificationServiceWrapper.assertNotificationsCount(4);
320         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(),
321             InstructionStatus.Executing);
322         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(),
323             InstructionStatus.Unknown);
324         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(),
325             InstructionStatus.Cancelled);
326     }
327
328     // TODO test deadline with state Queued
329
330     @Test
331     public void testSuccessExecutingWithDependenciesTransaction() throws Exception {
332         final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
333         final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
334
335         final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
336         final ListenableFuture<Instruction> future2 = this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
337
338         this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
339
340         Instruction i = future.get();
341         i.checkedExecutionStart();
342         i.executionCompleted(InstructionStatus.Successful, getDetails());
343
344         this.mockedNotificationServiceWrapper.assertNotificationsCount(4);
345         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(),
346             InstructionStatus.Executing);
347         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(),
348             InstructionStatus.Successful);
349         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(),
350             InstructionStatus.Scheduled);
351
352         i = future2.get();
353         i.checkedExecutionStart();
354         i.executionCompleted(InstructionStatus.Successful, getDetails());
355
356         this.mockedNotificationServiceWrapper.assertNotificationsCount(6);
357         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(4, mockedSubmit2.getId(),
358             InstructionStatus.Executing);
359         this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(5, mockedSubmit2.getId(),
360             InstructionStatus.Successful);
361     }
362
363     private Details getDetails() {
364         return new DetailsBuilder().build();
365     }
366
367     private SubmitInstructionInput getMockedSubmitInstructionInput(final String id, final String... dependencyIds) {
368         return getMockedSubmitInstructionInput(id, Optional.empty(), dependencyIds);
369     }
370
371     private SubmitInstructionInput getMockedSubmitInstructionInput(final String id, final Optional<Nanotime> deadline,
372         final String... dependencyIds) {
373         final SubmitInstructionInput mockedSubmitInstruction = mock(SubmitInstructionInput.class);
374
375         doReturn(PcepUpdateTunnelInput.class).when(mockedSubmitInstruction).getImplementedInterface();
376         final List<InstructionId> dependencies = Lists.newArrayList();
377         for (final String dependencyId : dependencyIds) {
378             dependencies.add(getInstructionId(dependencyId));
379         }
380
381         doReturn(dependencies).when(mockedSubmitInstruction).getPreconditions();
382         doReturn(getInstructionId(id)).when(mockedSubmitInstruction).getId();
383         doReturn(deadline.isPresent() ? deadline.get() : new Nanotime(BigInteger.valueOf(Long.MAX_VALUE)))
384             .when(mockedSubmitInstruction).getDeadline();
385         return mockedSubmitInstruction;
386     }
387
388     private CancelInstructionInput getCancelInstruction(final String instructionId) {
389         final CancelInstructionInputBuilder builder = new CancelInstructionInputBuilder();
390         builder.setId(getInstructionId(instructionId));
391         return builder.build();
392     }
393
394     private InstructionId getInstructionId(final String id) {
395         return new InstructionId(id);
396     }
397
398     private KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.
399         rev150720.instruction.queue.Instruction, InstructionKey> buildInstructionIID(final InstructionId id) {
400         return InstanceIdentifier.builder(InstructionsQueue.class, new InstructionsQueueKey(INSTRUCTIONS_QUEUE_KEY))
401             .build().child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720
402                 .instruction.queue.Instruction.class, new InstructionKey(id));
403     }
404 }