From 18acba617040ba7eeec5a475d00bfe742dfbd46a Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Wed, 8 Jan 2014 11:57:44 +0100 Subject: [PATCH] Added test for ProgrammingServiceImpl Tested -scheduling, canceling, executing and cleaning of transactions with or without dependencies -timeout of instructions in different states with or without dependencies Change-Id: I2fa5d592c31fe93227c535f2c7deb015aaf4f2e2 Signed-off-by: Maros Marsalek --- programming/impl/pom.xml | 10 + .../programming/impl/InstructionImpl.java | 21 +- .../impl/ProgrammingServiceImpl.java | 36 +- .../impl/MockedDataProviderWrapper.java | 172 ++++++++ .../impl/MockedExecutorWrapper.java | 61 +++ .../MockedNotificationServiceWrapper.java | 66 ++++ .../impl/ProgrammingServiceImplTest.java | 366 ++++++++++++++++++ 7 files changed, 702 insertions(+), 30 deletions(-) create mode 100644 programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedDataProviderWrapper.java create mode 100644 programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedExecutorWrapper.java create mode 100644 programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedNotificationServiceWrapper.java create mode 100644 programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImplTest.java diff --git a/programming/impl/pom.xml b/programming/impl/pom.xml index 65fc2862be..4efc2b8a9d 100644 --- a/programming/impl/pom.xml +++ b/programming/impl/pom.xml @@ -50,6 +50,16 @@ + + junit + junit + + + ${project.groupId} + pcep-tunnel-api + ${project.version} + test + ${project.groupId} mockito-configuration diff --git a/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/InstructionImpl.java b/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/InstructionImpl.java index e342405ee2..3e0ab9a024 100644 --- a/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/InstructionImpl.java +++ b/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/InstructionImpl.java @@ -7,14 +7,11 @@ */ package org.opendaylight.bgpcep.programming.impl; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; import io.netty.util.Timeout; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.annotation.concurrent.GuardedBy; - import org.opendaylight.bgpcep.programming.spi.ExecutionResult; import org.opendaylight.bgpcep.programming.spi.Instruction; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.CancelFailure; @@ -26,10 +23,10 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programm import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; +import javax.annotation.concurrent.GuardedBy; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; final class InstructionImpl implements Instruction { private static final Logger LOG = LoggerFactory.getLogger(InstructionImpl.class); @@ -218,6 +215,8 @@ final class InstructionImpl implements Instruction { it.next().removeDependency(this); } dependants.clear(); + + this.queue.instructionRemoved(); } synchronized ListenableFuture> ready() { diff --git a/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImpl.java b/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImpl.java index de8bf65ccc..634534eb36 100644 --- a/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImpl.java +++ b/programming/impl/src/main/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImpl.java @@ -7,20 +7,15 @@ */ package org.opendaylight.bgpcep.programming.impl; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.SettableFuture; import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - import org.opendaylight.bgpcep.programming.NanotimeUtil; import org.opendaylight.bgpcep.programming.spi.ExecutionResult; import org.opendaylight.bgpcep.programming.spi.Instruction; @@ -58,12 +53,15 @@ import org.opendaylight.yangtools.yang.common.RpcResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.SettableFuture; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; public final class ProgrammingServiceImpl implements AutoCloseable, InstructionScheduler, ProgrammingService { private static final Logger LOG = LoggerFactory.getLogger(ProgrammingServiceImpl.class); @@ -162,7 +160,7 @@ public final class ProgrammingServiceImpl implements AutoCloseable, InstructionS for (final InstructionId id : input.getId()) { // Find the instruction - final InstructionImpl i = this.insns.get(input.getId()); + final InstructionImpl i = this.insns.get(id); if (i == null) { LOG.debug("Instruction {} not present in the graph", input.getId()); failed.add(id); @@ -251,7 +249,7 @@ public final class ProgrammingServiceImpl implements AutoCloseable, InstructionS * and fail the operation. */ if (!unmet.isEmpty()) { - throw new SchedulerException("Instruction's dependecies are already unsuccessful", new FailureBuilder().setType(DeadOnArrival.class).setFailedPreconditions(unmet).build()); + throw new SchedulerException("Instruction's dependencies are already unsuccessful", new FailureBuilder().setType(DeadOnArrival.class).setFailedPreconditions(unmet).build()); } /* diff --git a/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedDataProviderWrapper.java b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedDataProviderWrapper.java new file mode 100644 index 0000000000..e07326a295 --- /dev/null +++ b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedDataProviderWrapper.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.programming.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import org.mockito.Matchers; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; +import org.opendaylight.controller.sal.binding.api.data.DataProviderService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.Nanotime; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.instruction.queue.Instruction; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.instruction.queue.InstructionKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.instruction.queue.Instructions; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +import java.util.List; +import java.util.concurrent.Future; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +final class MockedDataProviderWrapper { + + private DataModificationTransaction lastMockedTransaction; + private DataProviderService lastMockedDataProvider; + + private List putOperationalDataInvocations; + private List> removeOperationalDataInvocations; + + MockedDataProviderWrapper() { + MockitoAnnotations.initMocks(this); + putOperationalDataInvocations = Lists.newArrayList(); + removeOperationalDataInvocations = Lists.newArrayList(); + } + + public DataProviderService getMockedDataProvider() { + lastMockedTransaction = setupMockedTransaction(); + lastMockedDataProvider = setupMockedDataProvider(lastMockedTransaction); + return lastMockedDataProvider; + } + + private DataProviderService setupMockedDataProvider(DataModificationTransaction mockedTransaction) { + DataProviderService mockedDataProvider = mock(DataProviderService.class); + doReturn(mockedTransaction).when(mockedDataProvider).beginTransaction(); + + Future mockedCommitFuture = mock(Future.class); + doReturn(mockedCommitFuture).when(mockedTransaction).commit(); + return mockedDataProvider; + } + + private DataModificationTransaction setupMockedTransaction() { + DataModificationTransaction mockedTransaction = mock(DataModificationTransaction.class); + + doReturn(null).when(mockedTransaction).readOperationalData( + Matchers.>any()); + + setPutAnswer(mockedTransaction); + setRemoveAnwer(mockedTransaction); + return mockedTransaction; + } + + private void setRemoveAnwer(DataModificationTransaction mockedTransaction) { + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + removeOperationalDataInvocations.add((InstanceIdentifier) invocation.getArguments()[0]); + return null; + } + }).when(mockedTransaction).removeOperationalData( + Matchers.> any()); + } + + private void setPutAnswer(DataModificationTransaction mockedTransaction) { + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + putOperationalDataInvocations.add(PutOperationalDataInvocationArgs.fromObjects( + invocation.getArguments()[0], invocation.getArguments()[1])); + return null; + } + }).when(mockedTransaction).putOperationalData(Matchers.>any(), + any(DataObject.class)); + } + + MockedDataProviderWrapper verifyBeginTransaction(int beginTransactionCount) { + verify(lastMockedDataProvider, times(beginTransactionCount)).beginTransaction(); + return this; + } + + MockedDataProviderWrapper verifyPutDataOnTransaction(int putCount) { + verify(lastMockedTransaction, times(putCount)).putOperationalData(Matchers.>any(), + any(DataObject.class)); + return this; + } + + MockedDataProviderWrapper verifyRemoveDataOnTransaction(int removeCount) { + verify(lastMockedTransaction, times(removeCount)).removeOperationalData(Matchers.>any()); + return this; + } + + MockedDataProviderWrapper verifyCommitTransaction(int commitCount) { + verify(lastMockedTransaction, times(commitCount)).commit(); + return this; + } + + void assertPutDataForInstructions(int idx, InstructionId expectedId, Nanotime expectedDeadline) { + assertPutDataTargetType(idx, Instruction.class); + assertEquals(Instructions.class, putOperationalDataInvocations.get(idx).data.getImplementedInterface()); + Instructions instructions = (Instructions) putOperationalDataInvocations.get(idx).data; + assertEquals(expectedId, instructions.getId()); + if(expectedDeadline!=null) + assertEquals(expectedDeadline, instructions.getDeadline()); + } + + public void assertPutDataTargetType(int idx, Class targetType) { + assertEquals(targetType, putOperationalDataInvocations.get(idx).id.getTargetType()); + } + + public void assertRemoveDataForInstruction(int idx, InstructionId expectedId) { + assertEquals(Instruction.class, removeOperationalDataInvocations.get(idx).getTargetType()); + InstanceIdentifier instanceId = removeOperationalDataInvocations.get(idx) + .firstIdentifierOf(Instruction.class); + assertNotNull(instanceId); + + InstanceIdentifier.PathArgument instructionPathArg = instanceId.getPathArguments().get(1); + assertTrue(instructionPathArg instanceof InstanceIdentifier.IdentifiableItem); + InstructionKey expectedKey = new InstructionKey(expectedId); + assertEquals(expectedKey, ((InstanceIdentifier.IdentifiableItem) instructionPathArg).getKey()); + } + + static final class PutOperationalDataInvocationArgs { + private final InstanceIdentifier id; + private final DataObject data; + + private PutOperationalDataInvocationArgs(InstanceIdentifier id, DataObject data) { + this.id = id; + this.data = data; + } + + static PutOperationalDataInvocationArgs fromObjects(Object id, Object data) { + Preconditions.checkArgument(id instanceof InstanceIdentifier); + Preconditions.checkArgument(data instanceof DataObject); + return new PutOperationalDataInvocationArgs((InstanceIdentifier)id, (DataObject)data); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("PutOperationalDataInvocationArgs{"); + sb.append("id=").append(id); + sb.append(", data=").append(data); + sb.append('}'); + return sb.toString(); + } + } +} diff --git a/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedExecutorWrapper.java b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedExecutorWrapper.java new file mode 100644 index 0000000000..35bfa6be84 --- /dev/null +++ b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedExecutorWrapper.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.programming.impl; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.List; +import java.util.concurrent.Callable; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +final class MockedExecutorWrapper { + + private List submittedTasksToExecutor; + + MockedExecutorWrapper() { + submittedTasksToExecutor = Lists.newArrayList(); + } + + ListeningExecutorService getMockedExecutor() { + ListeningExecutorService mockedExecutor = mock(ListeningExecutorService.class); + Answer> submitAnswer = new Answer>() { + @Override + public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { + Object task = invocation.getArguments()[0]; + submittedTasksToExecutor.add(task); + + Object result = null; + if(task instanceof Runnable) { + ((Runnable)task).run(); + } else if(task instanceof Callable) { + result = ((Callable)task).call(); + } + + ListenableFuture mockedFuture = mock(ListenableFuture.class); + doReturn(result).when(mockedFuture).get(); + return mockedFuture; + } + }; + doAnswer(submitAnswer).when(mockedExecutor).submit(any(Runnable.class)); + doAnswer(submitAnswer).when(mockedExecutor).submit(any(Callable.class)); + return mockedExecutor; + } + + void assertSubmittedTasksSize(int taskCount) { + assertEquals(taskCount, submittedTasksToExecutor.size()); + } +} diff --git a/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedNotificationServiceWrapper.java b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedNotificationServiceWrapper.java new file mode 100644 index 0000000000..3051508aa8 --- /dev/null +++ b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/MockedNotificationServiceWrapper.java @@ -0,0 +1,66 @@ +/** + * @author Maros Marsalek + * + * 01 2014 + * + * Copyright (c) 2012 by Cisco Systems, Inc. + * All rights reserved. + */ +package org.opendaylight.bgpcep.programming.impl; + +import com.google.common.collect.Lists; +import junit.framework.Assert; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.opendaylight.controller.sal.binding.api.NotificationProviderService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionStatus; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionStatusChanged; +import org.opendaylight.yangtools.yang.binding.Notification; + +import java.util.List; + +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +final class MockedNotificationServiceWrapper { + private List publishedNotifications; + + MockedNotificationServiceWrapper() { + publishedNotifications = Lists.newArrayList(); + } + + NotificationProviderService getMockedNotificationService() { + NotificationProviderService mockedNotificationService = mock(NotificationProviderService.class); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object notif = invocation.getArguments()[0]; + assertTrue(Notification.class.isAssignableFrom(notif.getClass())); + publishedNotifications.add((Notification) notif); + return null; + } + }).when(mockedNotificationService).publish(any(Notification.class)); + return mockedNotificationService; + } + + void assertNotificationsCount(int count) { + assertEquals(count, publishedNotifications.size()); + } + + public void assertInstructionStatusChangedNotification(int idx, InstructionId id, InstructionStatus status) { + assertTrue(InstructionStatusChanged.class.isAssignableFrom(publishedNotifications.get(idx).getClass())); + InstructionStatusChanged firstNotification = (InstructionStatusChanged) publishedNotifications.get(idx); + assertInstructionStatusChangedNotification(id, status, firstNotification); + } + + private void assertInstructionStatusChangedNotification(InstructionId id, InstructionStatus status, + InstructionStatusChanged firstNotification) { + Assert.assertEquals(id, firstNotification.getId()); + Assert.assertEquals(status, firstNotification.getStatus()); + } +} diff --git a/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImplTest.java b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImplTest.java new file mode 100644 index 0000000000..731c62bd08 --- /dev/null +++ b/programming/impl/src/test/java/org/opendaylight/bgpcep/programming/impl/ProgrammingServiceImplTest.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.programming.impl; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import junit.framework.Assert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.matchers.JUnitMatchers; +import org.opendaylight.bgpcep.programming.NanotimeUtil; +import org.opendaylight.bgpcep.programming.spi.Instruction; +import org.opendaylight.bgpcep.programming.spi.SchedulerException; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.CancelInstructionInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.CancelInstructionInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.CleanInstructionsInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.CleanInstructionsInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.CleanInstructionsOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionQueue; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.InstructionStatus; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.Nanotime; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.SubmitInstructionInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.instruction.status.changed.Details; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev130930.instruction.status.changed.DetailsBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.programming.rev131030.PcepUpdateTunnelInput; +import org.opendaylight.yangtools.yang.common.RpcResult; + +import java.math.BigInteger; +import java.util.List; + +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class ProgrammingServiceImplTest { + + public static final int INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS = 3; + + private ProgrammingServiceImpl testedProgrammingService; + private MockedExecutorWrapper mockedExecutorWrapper; + private MockedDataProviderWrapper mockedDataProviderWrapper; + private MockedNotificationServiceWrapper mockedNotificationServiceWrapper; + private Timer timer = new HashedWheelTimer(); + + @Before + public void setUp() throws Exception { + mockedDataProviderWrapper = new MockedDataProviderWrapper(); + mockedExecutorWrapper = new MockedExecutorWrapper(); + mockedNotificationServiceWrapper = new MockedNotificationServiceWrapper(); + + testedProgrammingService = new ProgrammingServiceImpl(mockedDataProviderWrapper.getMockedDataProvider(), + mockedNotificationServiceWrapper.getMockedNotificationService(), + mockedExecutorWrapper.getMockedExecutor(), timer); + + mockedDataProviderWrapper.verifyBeginTransaction(1).verifyPutDataOnTransaction(1).verifyCommitTransaction(1); + mockedDataProviderWrapper.assertPutDataTargetType(0, InstructionQueue.class); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testScheduleInstruction() throws Exception { + SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit"); + testedProgrammingService.scheduleInstruction(mockedSubmit); + + // assert Schedule to executor + mockedExecutorWrapper.assertSubmittedTasksSize(1); + + // assert Notification + mockedNotificationServiceWrapper.assertNotificationsCount(1); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(0, mockedSubmit.getId(), + InstructionStatus.Scheduled); + + mockedDataProviderWrapper.verifyBeginTransaction(2).verifyPutDataOnTransaction(2).verifyCommitTransaction(2) + .verifyRemoveDataOnTransaction(0); + mockedDataProviderWrapper.assertPutDataForInstructions(1, mockedSubmit.getId(), mockedSubmit.getDeadline()); + } + + @Test + public void testScheduleDependingInstruction() throws Exception { + testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit1")); + SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit2); + + mockedExecutorWrapper.assertSubmittedTasksSize(2); + + // First is in state scheduled, so second could not be scheduled yet + mockedNotificationServiceWrapper.assertNotificationsCount(1); + } + + @Test + public void testScheduleDependingInstructionToFail() throws Exception { + try { + testedProgrammingService.scheduleInstruction(getMockedSubmitInstructionInput("mockedSubmit", "dep1")); + } catch (SchedulerException e) { + assertThat(e.getMessage(), JUnitMatchers.containsString("Unknown dependency ID")); + mockedNotificationServiceWrapper.assertNotificationsCount(0); + return; + } + fail("Instruction schedule should fail on unresolved dependencies"); + } + + @Test + public void testCancelInstruction() throws Exception { + SubmitInstructionInput mockedSubmit = getMockedSubmitInstructionInput("mockedSubmit"); + testedProgrammingService.scheduleInstruction(mockedSubmit); + + CancelInstructionInput mockedCancel = getCancelInstruction("mockedSubmit"); + testedProgrammingService.cancelInstruction(mockedCancel); + + mockedExecutorWrapper.assertSubmittedTasksSize(2); + + mockedNotificationServiceWrapper.assertNotificationsCount(2); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit.getId(), + InstructionStatus.Cancelled); + + mockedDataProviderWrapper.verifyBeginTransaction(2/*init + schedule first*/ + 1 /*cancel*/). + verifyPutDataOnTransaction(3).verifyRemoveDataOnTransaction(0).verifyCommitTransaction(3); + + mockedDataProviderWrapper.assertPutDataForInstructions(1, mockedSubmit.getId(), mockedSubmit.getDeadline()); + + mockedDataProviderWrapper.assertPutDataForInstructions(2, mockedCancel.getId(), null); + } + + @Test + public void testCancelDependantInstruction() throws Exception { + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit1); + SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit2); + SubmitInstructionInput mockedSubmit3 = getMockedSubmitInstructionInput("mockedSubmit3", "mockedSubmit1", + "mockedSubmit2"); + testedProgrammingService.scheduleInstruction(mockedSubmit3); + + testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1")); + + mockedNotificationServiceWrapper.assertNotificationsCount(1 /*First Scheduled*/ + 3 /*First and all dependencies cancelled*/); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(0, mockedSubmit1.getId(), + InstructionStatus.Scheduled); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), + InstructionStatus.Cancelled); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit2.getId(), + InstructionStatus.Cancelled); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit3.getId(), + InstructionStatus.Cancelled); + } + + @Test + public void testCleanInstructions() throws Exception { + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit1); + SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit2); + + CleanInstructionsInputBuilder cleanInstructionsInputBuilder = new CleanInstructionsInputBuilder(); + CleanInstructionsInput cleanInstructionsInput = cleanInstructionsInputBuilder.setId( + Lists.newArrayList(mockedSubmit1.getId(), mockedSubmit2.getId())).build(); + + ListenableFuture> cleanedInstructionOutput = testedProgrammingService + .cleanInstructions(cleanInstructionsInput); + + assertCleanInstructionOutput(cleanedInstructionOutput, 2); + + int expectedBeginTxCount = 1/*Service init*/ + 1; + mockedDataProviderWrapper.verifyBeginTransaction(expectedBeginTxCount/*Schedule first*/). + verifyCommitTransaction(expectedBeginTxCount) + .verifyPutDataOnTransaction(expectedBeginTxCount) + .verifyRemoveDataOnTransaction(0); + + testedProgrammingService.cancelInstruction(getCancelInstruction("mockedSubmit1")); + + cleanedInstructionOutput = testedProgrammingService.cleanInstructions(cleanInstructionsInput); + assertCleanInstructionOutput(cleanedInstructionOutput, 0); + mockedDataProviderWrapper.verifyBeginTransaction(expectedBeginTxCount + 2/*Update status to cancelled*/ + 2 /*Cleanup*/) + .verifyRemoveDataOnTransaction(2) + .verifyPutDataOnTransaction(2/*From before*/ + 2/*Cancel*/) + .verifyCommitTransaction(expectedBeginTxCount + 2/*Update status to cancelled*/ + 2 /*Cleanup*/); + + mockedDataProviderWrapper.assertRemoveDataForInstruction(0, mockedSubmit1.getId()); + mockedDataProviderWrapper.assertRemoveDataForInstruction(1, mockedSubmit2.getId()); + } + + private void assertCleanInstructionOutput( + ListenableFuture> cleanedInstructionOutput, int unflushedCount) + throws InterruptedException, java.util.concurrent.ExecutionException { + if(unflushedCount==0) { + Assert.assertNull(cleanedInstructionOutput.get().getResult().getUnflushed()); + } else { + Assert.assertEquals(unflushedCount, cleanedInstructionOutput.get().getResult().getUnflushed().size()); + } + Assert.assertEquals(0, cleanedInstructionOutput.get().getErrors().size()); + } + + @Test + public void testCloseProgrammingService() throws Exception { + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit1); + SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit2); + + testedProgrammingService.close(); + + mockedNotificationServiceWrapper.assertNotificationsCount(1/* First scheduled */ + 2/* Both cancelled at close */); + mockedDataProviderWrapper.verifyRemoveDataOnTransaction(1); + } + + @Test(timeout = 30 * 1000) + public void testTimeoutWhileScheduledTransaction() throws Exception { + BigInteger deadlineOffset = BigInteger.valueOf(1000l * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */); + Nanotime current = NanotimeUtil.currentTime(); + Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset)); + + Optional deadline = Optional.of(deadlineNano); + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline); + ListenableFuture future = testedProgrammingService.scheduleInstruction(mockedSubmit1); + + mockedNotificationServiceWrapper.assertNotificationsCount(1); + + future.get(); + + Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000); + + mockedNotificationServiceWrapper.assertNotificationsCount(2); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), + InstructionStatus.Cancelled); + } + + @Test(timeout = 30 * 1000) + public void testTimeoutWhileSuccessfulTransaction() throws Exception { + BigInteger deadlineOffset = BigInteger.valueOf(1000l * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */); + Nanotime current = NanotimeUtil.currentTime(); + Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset)); + + Optional deadline = Optional.of(deadlineNano); + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline); + ListenableFuture future = testedProgrammingService.scheduleInstruction(mockedSubmit1); + + mockedNotificationServiceWrapper.assertNotificationsCount(1); + + Instruction i = future.get(); + i.checkedExecutionStart(); + i.executionCompleted(InstructionStatus.Successful, getDetails()); + + Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000); + + mockedNotificationServiceWrapper.assertNotificationsCount(3); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), + InstructionStatus.Executing); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), + InstructionStatus.Successful); + // Timeout in success should not do anything + } + + @Test(timeout = 30 * 1000) + public void testTimeoutWhileExecutingWithDependenciesTransaction() throws Exception { + BigInteger deadlineOffset = BigInteger.valueOf(1000l * 1000 * 1000 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS /* seconds */); + Nanotime current = NanotimeUtil.currentTime(); + Nanotime deadlineNano = new Nanotime(current.getValue().add(deadlineOffset)); + + Optional deadline = Optional.of(deadlineNano); + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1", deadline); + ListenableFuture future = testedProgrammingService.scheduleInstruction(mockedSubmit1); + + SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1"); + testedProgrammingService.scheduleInstruction(mockedSubmit2); + + mockedNotificationServiceWrapper.assertNotificationsCount(1); + + Instruction i = future.get(); + i.checkedExecutionStart(); + + Thread.sleep(2 * INSTRUCTION_DEADLINE_OFFSET_IN_SECONDS * 1000); + + mockedNotificationServiceWrapper.assertNotificationsCount(4); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), + InstructionStatus.Executing); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), + InstructionStatus.Unknown); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(), + InstructionStatus.Cancelled); + } + + // TODO test deadline with state Queued + + @Test + public void testSuccessExecutingWithDependenciesTransaction() throws Exception { + SubmitInstructionInput mockedSubmit1 = getMockedSubmitInstructionInput("mockedSubmit1"); + ListenableFuture future = testedProgrammingService.scheduleInstruction(mockedSubmit1); + + SubmitInstructionInput mockedSubmit2 = getMockedSubmitInstructionInput("mockedSubmit2", "mockedSubmit1"); + ListenableFuture future2 = testedProgrammingService.scheduleInstruction(mockedSubmit2); + + mockedNotificationServiceWrapper.assertNotificationsCount(1); + + Instruction i = future.get(); + i.checkedExecutionStart(); + i.executionCompleted(InstructionStatus.Successful, getDetails()); + + + mockedNotificationServiceWrapper.assertNotificationsCount(4); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(1, mockedSubmit1.getId(), + InstructionStatus.Executing); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(2, mockedSubmit1.getId(), + InstructionStatus.Successful); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(3, mockedSubmit2.getId(), + InstructionStatus.Scheduled); + + i = future2.get(); + i.checkedExecutionStart(); + i.executionCompleted(InstructionStatus.Successful, getDetails()); + + mockedNotificationServiceWrapper.assertNotificationsCount(6); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(4, mockedSubmit2.getId(), + InstructionStatus.Executing); + mockedNotificationServiceWrapper.assertInstructionStatusChangedNotification(5, mockedSubmit2.getId(), + InstructionStatus.Successful); + } + + private Details getDetails() { + return new DetailsBuilder().build(); + } + + private SubmitInstructionInput getMockedSubmitInstructionInput(String id, String... dependencyIds) { + return getMockedSubmitInstructionInput(id, Optional.absent(), dependencyIds); + } + + private SubmitInstructionInput getMockedSubmitInstructionInput(String id, Optional deadline, String... dependencyIds) { + SubmitInstructionInput mockedSubmitInstruction = mock(SubmitInstructionInput.class); + + doReturn(PcepUpdateTunnelInput.class).when(mockedSubmitInstruction).getImplementedInterface(); + List dependencies = Lists.newArrayList(); + for (String dependencyId : dependencyIds) { + dependencies.add(getInstructionId(dependencyId)); + } + + doReturn(dependencies).when(mockedSubmitInstruction).getPreconditions(); + doReturn(getInstructionId(id)).when(mockedSubmitInstruction).getId(); + doReturn(deadline.isPresent() ? deadline.get() : new Nanotime(BigInteger.valueOf(Long.MAX_VALUE))).when( + mockedSubmitInstruction).getDeadline(); + return mockedSubmitInstruction; + } + + private CancelInstructionInput getCancelInstruction(String instructionId) { + CancelInstructionInputBuilder builder = new CancelInstructionInputBuilder(); + builder.setId(getInstructionId(instructionId)); + return builder.build(); + } + + private InstructionId getInstructionId(String id) { + return new InstructionId(id); + } + +} -- 2.36.6