2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.bgpcep.programming.impl;
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;
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;
66 public class ProgrammingServiceImplTest extends AbstractConcurrentDataBrokerTest {
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;
75 private ClusterSingletonServiceProvider cssp;
77 private ClusterSingletonServiceRegistration singletonServiceRegistration;
79 private RpcProviderRegistry rpcRegistry;
81 private RoutedRpcRegistration<ProgrammingService> registration;
82 private ClusterSingletonService singletonService;
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));
92 doAnswer(invocationOnMock -> {
93 this.singletonService.closeServiceInstance();
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();
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();
110 public void tearDown() throws Exception {
111 this.singletonService.closeServiceInstance();
112 this.testedProgrammingService.close();
116 public void testScheduleInstruction() throws Exception {
117 final SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit");
118 this.testedProgrammingService.scheduleInstruction(mockedSubmit);
120 checkEquals(()-> assertTrue(assertInstructionExists(mockedSubmit.getId())));
122 // assert Schedule to executor
123 this.mockedExecutorWrapper.assertSubmittedTasksSize(1);
125 // assert Notification
126 this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
127 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(0, mockedSubmit.getId(), InstructionStatus.Scheduled);
131 public void testScheduleDependingInstruction() throws Exception {
132 this.testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit1"));
133 final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
134 this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
136 this.mockedExecutorWrapper.assertSubmittedTasksSize(2);
138 // First is in state scheduled, so second could not be scheduled yet
139 this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
143 public void testScheduleDependingInstructionToFail() throws Exception {
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);
151 fail("Instruction schedule should fail on unresolved dependencies");
155 public void testCancelInstruction() throws Exception {
156 final SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit");
157 this.testedProgrammingService.scheduleInstruction(mockedSubmit);
159 checkEquals(()-> assertTrue(assertInstructionExists(mockedSubmit.getId())));
161 final CancelInstructionInput mockedCancel = getCancelInstruction("mockedSubmit");
162 this.testedProgrammingService.cancelInstruction(mockedCancel);
164 assertTrue(assertInstructionExists(mockedSubmit.getId()));
166 this.mockedExecutorWrapper.assertSubmittedTasksSize(2);
168 this.mockedNotificationServiceWrapper.assertNotificationsCount(2);
169 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit.getId(), InstructionStatus.Cancelled);
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);
181 this.testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1"));
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);
189 checkEquals(()-> assertTrue(assertInstructionExists(mockedSubmit1.getId())));
190 checkEquals(()-> assertTrue(assertInstructionExists(mockedSubmit2.getId())));
191 checkEquals(()-> assertTrue(assertInstructionExists(mockedSubmit3.getId())));
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);
201 final CleanInstructionsInputBuilder cleanInstructionsInputBuilder = new CleanInstructionsInputBuilder();
202 final CleanInstructionsInput cleanInstructionsInput = cleanInstructionsInputBuilder.setId(
203 Lists.newArrayList(mockedSubmit1.getId(), mockedSubmit2.getId())).build();
205 ListenableFuture<RpcResult<CleanInstructionsOutput>> cleanedInstructionOutput = this.testedProgrammingService.cleanInstructions(cleanInstructionsInput);
207 assertCleanInstructionOutput(cleanedInstructionOutput, 2);
209 this.testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1"));
211 cleanedInstructionOutput = this.testedProgrammingService.cleanInstructions(cleanInstructionsInput);
212 assertCleanInstructionOutput(cleanedInstructionOutput, 0);
214 checkEquals(()-> assertFalse(assertInstructionExists(mockedSubmit1.getId())));
215 checkEquals(()-> assertFalse(assertInstructionExists(mockedSubmit2.getId())));
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());
224 assertEquals(unflushedCount, cleanedInstructionOutput.get().getResult().getUnflushed().size());
226 assertEquals(0, cleanedInstructionOutput.get().getErrors().size());
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);
236 this.testedProgrammingService.close();
238 this.mockedNotificationServiceWrapper.assertNotificationsCount(1/* First scheduled */+ 2/* Both cancelled at close */);
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));
247 final Optional<Nanotime> deadline = Optional.of(deadlineNano);
248 final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
249 final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
251 this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
255 Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000);
257 this.mockedNotificationServiceWrapper.assertNotificationsCount(2);
258 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Cancelled);
261 @Test(timeout = 30 * 1000)
262 public void testTimeoutWhileSuccessfulTransaction() throws Exception {
263 final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
264 final Nanotime current = NanotimeUtil.currentTime();
265 final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
267 final Optional<Nanotime> deadline = Optional.of(deadlineNano);
268 final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
269 final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
271 this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
273 final Instruction i = future.get();
274 i.checkedExecutionStart();
275 i.executionCompleted(InstructionStatus.Successful, getDetails());
277 Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000);
279 this.mockedNotificationServiceWrapper.assertNotificationsCount(3);
280 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Executing);
281 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), InstructionStatus.Successful);
282 // Timeout in success should not do anything
285 @Test(timeout = 30 * 1000)
286 public void testTimeoutWhileExecutingWithDependenciesTransaction() throws Exception {
287 final BigInteger deadlineOffset = BigInteger.valueOf(1000L * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */);
288 final Nanotime current = NanotimeUtil.currentTime();
289 final Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset));
291 final Optional<Nanotime> deadline = Optional.of(deadlineNano);
292 final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline);
293 final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
295 final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
296 this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
298 this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
300 final Instruction i = future.get();
301 i.checkedExecutionStart();
303 Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000);
305 this.mockedNotificationServiceWrapper.assertNotificationsCount(4);
306 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Executing);
307 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), InstructionStatus.Unknown);
308 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(), InstructionStatus.Cancelled);
311 // TODO test deadline with state Queued
314 public void testSuccessExecutingWithDependenciesTransaction() throws Exception {
315 final SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1");
316 final ListenableFuture<Instruction> future = this.testedProgrammingService.scheduleInstruction(mockedSubmit1);
318 final SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1");
319 final ListenableFuture<Instruction> future2 = this.testedProgrammingService.scheduleInstruction(mockedSubmit2);
321 this.mockedNotificationServiceWrapper.assertNotificationsCount(1);
323 Instruction i = future.get();
324 i.checkedExecutionStart();
325 i.executionCompleted(InstructionStatus.Successful, getDetails());
327 this.mockedNotificationServiceWrapper.assertNotificationsCount(4);
328 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), InstructionStatus.Executing);
329 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), InstructionStatus.Successful);
330 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(), InstructionStatus.Scheduled);
333 i.checkedExecutionStart();
334 i.executionCompleted(InstructionStatus.Successful, getDetails());
336 this.mockedNotificationServiceWrapper.assertNotificationsCount(6);
337 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(4, mockedSubmit2.getId(), InstructionStatus.Executing);
338 this.mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(5, mockedSubmit2.getId(), InstructionStatus.Successful);
341 private Details getDetails() {
342 return new DetailsBuilder().build();
345 private SubmitInstructionInput getMockedSubmitInstructionInput(final String id, final String... dependencyIds) {
346 return getMockedSubmitInstructionInput(id, Optional.empty(), dependencyIds);
349 private SubmitInstructionInput getMockedSubmitInstructionInput(final String id, final Optional<Nanotime> deadline, final String... dependencyIds) {
350 final SubmitInstructionInput mockedSubmitInstruction = mock(SubmitInstructionInput.class);
352 doReturn(PcepUpdateTunnelInput.class).when(mockedSubmitInstruction).getImplementedInterface();
353 final List<InstructionId> dependencies = Lists.newArrayList();
354 for (final String dependencyId : dependencyIds) {
355 dependencies.add(getInstructionId(dependencyId));
358 doReturn(dependencies).when(mockedSubmitInstruction).getPreconditions();
359 doReturn(getInstructionId(id)).when(mockedSubmitInstruction).getId();
360 doReturn(deadline.isPresent() ? deadline.get() : new Nanotime(BigInteger.valueOf(Long.MAX_VALUE))).when(mockedSubmitInstruction).getDeadline();
361 return mockedSubmitInstruction;
364 private CancelInstructionInput getCancelInstruction(final String instructionId) {
365 final CancelInstructionInputBuilder builder = new CancelInstructionInputBuilder();
366 builder.setId(getInstructionId(instructionId));
367 return builder.build();
370 private InstructionId getInstructionId(final String id) {
371 return new InstructionId(id);
374 private boolean assertInstructionExists(final InstructionId id) {
376 return getDataBroker().newReadOnlyTransaction().read(LogicalDatastoreType.OPERATIONAL,
377 InstanceIdentifier.builder(InstructionsQueue.class, new InstructionsQueueKey(INSTRUCTIONS_QUEUE_KEY))
378 .build().child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.queue.Instruction.class,
379 new InstructionKey(id))).get().isPresent();
380 } catch (InterruptedException | ExecutionException e) {