/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.workflow.impl; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.junit.Assert.assertEquals; import static org.opencastproject.workflow.api.WorkflowOperationResult.Action.CONTINUE; import static org.opencastproject.workflow.impl.SecurityServiceStub.DEFAULT_ORG_ADMIN; import org.opencastproject.job.api.Job; import org.opencastproject.job.api.JobContext; import org.opencastproject.job.api.JobImpl; import org.opencastproject.mediapackage.DefaultMediaPackageSerializerImpl; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.mediapackage.MediaPackageBuilder; import org.opencastproject.mediapackage.MediaPackageBuilderFactory; import org.opencastproject.mediapackage.MediaPackageException; import org.opencastproject.mediapackage.identifier.UUIDIdBuilderImpl; import org.opencastproject.message.broker.api.MessageSender; import org.opencastproject.metadata.api.MediaPackageMetadataService; import org.opencastproject.security.api.AccessControlList; import org.opencastproject.security.api.AclScope; import org.opencastproject.security.api.AuthorizationService; import org.opencastproject.security.api.DefaultOrganization; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.UserDirectoryService; import org.opencastproject.serviceregistry.api.IncidentService; import org.opencastproject.serviceregistry.api.ServiceRegistry; import org.opencastproject.serviceregistry.api.ServiceRegistryException; import org.opencastproject.serviceregistry.api.ServiceRegistryInMemoryImpl; import org.opencastproject.serviceregistry.impl.jpa.ServiceRegistrationJpaImpl; import org.opencastproject.util.ConfigurationException; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.data.Tuple; import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler; import org.opencastproject.workflow.api.RetryStrategy; import org.opencastproject.workflow.api.WorkflowDefinition; import org.opencastproject.workflow.api.WorkflowDefinitionImpl; import org.opencastproject.workflow.api.WorkflowInstance; import org.opencastproject.workflow.api.WorkflowInstance.WorkflowState; import org.opencastproject.workflow.api.WorkflowInstanceImpl; import org.opencastproject.workflow.api.WorkflowOperationDefinitionImpl; import org.opencastproject.workflow.api.WorkflowOperationException; import org.opencastproject.workflow.api.WorkflowOperationHandler; import org.opencastproject.workflow.api.WorkflowOperationInstance; import org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState; import org.opencastproject.workflow.api.WorkflowOperationInstanceImpl; import org.opencastproject.workflow.api.WorkflowOperationResult; import org.opencastproject.workflow.api.WorkflowOperationResult.Action; import org.opencastproject.workflow.api.WorkflowParser; import org.opencastproject.workflow.api.WorkflowQuery; import org.opencastproject.workflow.api.WorkflowSet; import org.opencastproject.workflow.api.WorkflowStateListener; import org.opencastproject.workflow.handler.workflow.ErrorResolutionWorkflowOperationHandler; import org.opencastproject.workflow.impl.WorkflowServiceImpl.HandlerRegistration; import org.opencastproject.workspace.api.Workspace; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.easymock.EasyMock; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import junit.framework.Assert; public class WorkflowServiceImplTest { private static final String REMOTE_SERVICE = "workflowService"; private static final String REMOTE_HOST = "http://entwinemedia:8080"; private WorkflowServiceImpl service = null; private WorkflowDefinitionScanner scanner = null; private WorkflowDefinition workingDefinition = null; private WorkflowDefinition failingDefinitionWithoutErrorHandler = null; private WorkflowDefinition failingDefinitionWithErrorHandler = null; private WorkflowDefinition pausingWorkflowDefinition = null; private MediaPackage mediapackage1 = null; private MediaPackage mediapackage2 = null; private SucceedingWorkflowOperationHandler succeedingOperationHandler = null; private WorkflowOperationHandler failingOperationHandler = null; private WorkflowServiceSolrIndex dao = null; protected Set<HandlerRegistration> handlerRegistrations = null; private Workspace workspace = null; private ServiceRegistryInMemoryImpl serviceRegistry = null; private SecurityService securityService = null; private File sRoot = null; private AccessControlList acl = new AccessControlList(); protected static final String getStorageRoot() { return "." + File.separator + "target" + File.separator + System.currentTimeMillis(); } @Before public void setUp() throws Exception { // always start with a fresh solr root directory sRoot = new File(getStorageRoot()); try { FileUtils.forceMkdir(sRoot); } catch (IOException e) { Assert.fail(e.getMessage()); } // create operation handlers for our workflows succeedingOperationHandler = new SucceedingWorkflowOperationHandler(); failingOperationHandler = new FailingWorkflowOperationHandler(); handlerRegistrations = new HashSet<HandlerRegistration>(); handlerRegistrations.add(new HandlerRegistration("op1", succeedingOperationHandler)); handlerRegistrations.add(new HandlerRegistration("op2", succeedingOperationHandler)); handlerRegistrations.add(new HandlerRegistration("op3", failingOperationHandler)); handlerRegistrations.add(new HandlerRegistration(WorkflowServiceImpl.ERROR_RESOLUTION_HANDLER_ID, new ErrorResolutionWorkflowOperationHandler())); handlerRegistrations.add(new HandlerRegistration("opPause", new ResumableTestWorkflowOperationHandler())); handlerRegistrations.add(new HandlerRegistration("failOnHost", new FailOnHostWorkflowOperationHandler())); handlerRegistrations.add(new HandlerRegistration("failOneTime", new FailOnceWorkflowOperationHandler())); scanner = new WorkflowDefinitionScanner(); // instantiate a service implementation and its DAO, overriding the methods that depend on the osgi runtime service = new WorkflowServiceImpl() { @Override public Set<HandlerRegistration> getRegisteredHandlers() { return handlerRegistrations; } }; // Add scanner to activate workflow service and store definitions service.addWorkflowDefinitionScanner(scanner); // security service DefaultOrganization organization = new DefaultOrganization(); securityService = createNiceMock(SecurityService.class); expect(securityService.getUser()).andReturn(SecurityServiceStub.DEFAULT_ORG_ADMIN).anyTimes(); expect(securityService.getOrganization()).andReturn(organization).anyTimes(); replay(securityService); service.setSecurityService(securityService); UserDirectoryService userDirectoryService = EasyMock.createMock(UserDirectoryService.class); expect(userDirectoryService.loadUser((String) EasyMock.anyObject())).andReturn(DEFAULT_ORG_ADMIN) .anyTimes(); replay(userDirectoryService); service.setUserDirectoryService(userDirectoryService); AuthorizationService authzService = createNiceMock(AuthorizationService.class); expect(authzService.getActiveAcl((MediaPackage) EasyMock.anyObject())) .andReturn(Tuple.tuple(acl, AclScope.Series)).anyTimes(); replay(authzService); service.setAuthorizationService(authzService); List<Organization> organizationList = new ArrayList<Organization>(); organizationList.add(organization); OrganizationDirectoryService organizationDirectoryService = EasyMock.createMock(OrganizationDirectoryService.class); expect(organizationDirectoryService.getOrganization((String) EasyMock.anyObject())) .andReturn(securityService.getOrganization()).anyTimes(); expect(organizationDirectoryService.getOrganizations()).andReturn(organizationList).anyTimes(); replay(organizationDirectoryService); service.setOrganizationDirectoryService(organizationDirectoryService); MediaPackageMetadataService mds = createNiceMock(MediaPackageMetadataService.class); replay(mds); service.addMetadataService(mds); workspace = createNiceMock(Workspace.class); expect(workspace.getCollectionContents((String) EasyMock.anyObject())).andReturn(new URI[0]); replay(workspace); IncidentService incidentService = createNiceMock(IncidentService.class); replay(incidentService); serviceRegistry = new ServiceRegistryInMemoryImpl(service, securityService, userDirectoryService, organizationDirectoryService, incidentService); serviceRegistry.registerHost(REMOTE_HOST, REMOTE_HOST, Runtime.getRuntime().totalMemory(), Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors()); serviceRegistry.registerService(REMOTE_SERVICE, REMOTE_HOST, "/path", true); service.setWorkspace(workspace); MessageSender messageSender = createNiceMock(MessageSender.class); replay(messageSender); dao = new WorkflowServiceSolrIndex(); dao.setServiceRegistry(serviceRegistry); dao.setSecurityService(securityService); dao.setOrgDirectory(organizationDirectoryService); dao.setAuthorizationService(authzService); dao.solrRoot = sRoot + File.separator + "solr." + System.currentTimeMillis(); dao.activate("System Admin"); service.setDao(dao); service.setServiceRegistry(serviceRegistry); service.setMessageSender(messageSender); service.activate(null); InputStream is = null; try { is = WorkflowServiceImplTest.class.getResourceAsStream("/workflow-definition-1.xml"); workingDefinition = WorkflowParser.parseWorkflowDefinition(is); IOUtils.closeQuietly(is); is = WorkflowServiceImplTest.class.getResourceAsStream("/workflow-definition-2.xml"); failingDefinitionWithoutErrorHandler = WorkflowParser.parseWorkflowDefinition(is); IOUtils.closeQuietly(is); is = WorkflowServiceImplTest.class.getResourceAsStream("/workflow-definition-3.xml"); failingDefinitionWithErrorHandler = WorkflowParser.parseWorkflowDefinition(is); IOUtils.closeQuietly(is); is = WorkflowServiceImplTest.class.getResourceAsStream("/workflow-definition-4.xml"); pausingWorkflowDefinition = WorkflowParser.parseWorkflowDefinition(is); IOUtils.closeQuietly(is); service.registerWorkflowDefinition(workingDefinition); service.registerWorkflowDefinition(failingDefinitionWithoutErrorHandler); service.registerWorkflowDefinition(failingDefinitionWithErrorHandler); service.registerWorkflowDefinition(pausingWorkflowDefinition); MediaPackageBuilder mediaPackageBuilder = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder(); mediaPackageBuilder.setSerializer(new DefaultMediaPackageSerializerImpl(new File("target/test-classes"))); is = WorkflowServiceImplTest.class.getResourceAsStream("/mediapackage-1.xml"); mediapackage1 = mediaPackageBuilder.loadFromXml(is); IOUtils.closeQuietly(is); is = WorkflowServiceImplTest.class.getResourceAsStream("/mediapackage-2.xml"); mediapackage2 = mediaPackageBuilder.loadFromXml(is); Assert.assertNotNull(mediapackage1.getIdentifier()); Assert.assertNotNull(mediapackage2.getIdentifier()); } catch (Exception e) { Assert.fail(e.getMessage()); } } @After public void tearDown() throws Exception { serviceRegistry.deactivate(); serviceRegistry.unRegisterService(REMOTE_SERVICE, REMOTE_HOST); dao.deactivate(); service.deactivate(); } @SuppressWarnings("unused") @Test public void testGetWorkflowInstanceById() throws Exception { WorkflowInstance instance = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowInstance instance2 = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); // verify that we can retrieve the workflow instance from the service by its ID WorkflowInstance instanceFromDb = service.getWorkflowById(instance.getId()); Assert.assertNotNull(instanceFromDb); MediaPackage mediapackageFromDb = instanceFromDb.getMediaPackage(); Assert.assertNotNull(mediapackageFromDb); Assert.assertEquals(mediapackage1.getIdentifier().toString(), mediapackageFromDb.getIdentifier().toString()); Assert.assertEquals(2, service.countWorkflowInstances()); } @Test public void testGetWorkflowByMediaPackageId() throws Exception { // Ensure that the database doesn't have a workflow instance with this media package Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals( 0, service.getWorkflowInstances(new WorkflowQuery().withMediaPackage(mediapackage1.getIdentifier().toString())) .size()); Assert.assertEquals( 0, service.getWorkflowInstances(new WorkflowQuery().withMediaPackage(mediapackage2.getIdentifier().toString())) .size()); WorkflowInstance instance = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowInstance instance2 = startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); WorkflowInstance instance3 = startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); Assert.assertEquals(WorkflowState.SUCCEEDED, service.getWorkflowById(instance.getId()).getState()); Assert.assertEquals(WorkflowState.SUCCEEDED, service.getWorkflowById(instance2.getId()).getState()); Assert.assertEquals(WorkflowState.SUCCEEDED, service.getWorkflowById(instance3.getId()).getState()); Assert.assertEquals(mediapackage1.getIdentifier().toString(), service.getWorkflowById(instance.getId()) .getMediaPackage().getIdentifier().toString()); Assert.assertEquals(mediapackage2.getIdentifier().toString(), service.getWorkflowById(instance2.getId()) .getMediaPackage().getIdentifier().toString()); Assert.assertEquals(mediapackage2.getIdentifier().toString(), service.getWorkflowById(instance3.getId()) .getMediaPackage().getIdentifier().toString()); WorkflowSet workflowsInDb = service.getWorkflowInstances(new WorkflowQuery().withMediaPackage(mediapackage1 .getIdentifier().toString())); Assert.assertEquals(1, workflowsInDb.getItems().length); } @Test public void testGetWorkflowByCreator() throws Exception { // Set different creators in the mediapackages String manfred = "Dr. Manfred Frisch"; mediapackage1.addCreator(manfred); mediapackage2.addCreator("Somebody else"); // Ensure that the database doesn't have any workflow instances with media packages with this creator Assert.assertEquals(0, service.countWorkflowInstances()); WorkflowInstance instance1 = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowInstance instance2 = startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); Assert.assertEquals(WorkflowState.SUCCEEDED, service.getWorkflowById(instance1.getId()).getState()); Assert.assertEquals(WorkflowState.SUCCEEDED, service.getWorkflowById(instance2.getId()).getState()); // Build the workflow query WorkflowQuery queryForManfred = new WorkflowQuery().withCreator(manfred); Assert.assertEquals(1, service.getWorkflowInstances(queryForManfred).getTotalCount()); Assert.assertEquals(instance1.getMediaPackage().getIdentifier().toString(), service.getWorkflowInstances(queryForManfred).getItems()[0].getMediaPackage().getIdentifier().toString()); } @Test public void testParentWorkflow() throws Exception { WorkflowInstance originalInstance = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowInstance childInstance = startAndWait(workingDefinition, mediapackage1, originalInstance.getId(), WorkflowState.SUCCEEDED); Assert.assertNotNull(service.getWorkflowById(childInstance.getId()).getParentId()); Assert.assertEquals(originalInstance.getId(), (long) service.getWorkflowById(childInstance.getId()).getParentId()); // Create a child workflow with a wrong parent id try { service.start(workingDefinition, mediapackage1, new Long(1876234678), null); Assert.fail("Workflows should not be started with bad parent IDs"); } catch (NotFoundException e) { } // the exception is expected } @Test public void testGetWorkflowByEpisodeId() throws Exception { String mediaPackageId = mediapackage1.getIdentifier().toString(); // Ensure that the database doesn't have a workflow instance with this episode Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals(0, service.getWorkflowInstances(new WorkflowQuery().withMediaPackage(mediaPackageId)).size()); startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowSet workflowsInDb = service.getWorkflowInstances(new WorkflowQuery().withMediaPackage(mediaPackageId)); Assert.assertEquals(1, workflowsInDb.getItems().length); } @Test public void testGetWorkflowByCurrentOperation() throws Exception { // Ensure that the database doesn't have a workflow instance in the "opPause" operation Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals(0, service.getWorkflowInstances(new WorkflowQuery().withCurrentOperation("opPause")).size()); startAndWait(pausingWorkflowDefinition, mediapackage1, WorkflowState.PAUSED); WorkflowSet workflowsInDb = service.getWorkflowInstances(new WorkflowQuery().withCurrentOperation("opPause")); Assert.assertEquals(1, workflowsInDb.getItems().length); } @Test public void testGetWorkflowByText() throws Exception { // Ensure that the database doesn't have any workflow instances Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals(0, service.getWorkflowInstances(new WorkflowQuery().withText("Climate").withCount(100).withStartPage(0)) .size()); startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowSet workflowsInDb = service.getWorkflowInstances(new WorkflowQuery().withText("Climate").withCount(100) .withStartPage(0)); Assert.assertEquals(1, workflowsInDb.getItems().length); Assert.assertEquals(1, service.getWorkflowInstances(new WorkflowQuery().withText("limate")).size()); Assert.assertEquals(1, service.getWorkflowInstances(new WorkflowQuery().withText("mate")).size()); Assert.assertEquals(1, service.getWorkflowInstances(new WorkflowQuery().withText("lima")).size()); } @Test public void testGetWorkflowSort() throws Exception { String contributor1 = "foo"; String contributor2 = "bar"; String contributor3 = "baz"; // Ensure that the database doesn't have any workflow instances Assert.assertEquals(0, service.countWorkflowInstances()); // set contributors (a multivalued field) mediapackage1.addContributor(contributor1); mediapackage1.addContributor(contributor2); mediapackage2.addContributor(contributor2); mediapackage2.addContributor(contributor3); // run the workflows startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); WorkflowSet workflowsWithContributor1 = service.getWorkflowInstances(new WorkflowQuery() .withContributor(contributor1)); WorkflowSet workflowsWithContributor2 = service.getWorkflowInstances(new WorkflowQuery() .withContributor(contributor2)); WorkflowSet workflowsWithContributor3 = service.getWorkflowInstances(new WorkflowQuery() .withContributor(contributor3)); Assert.assertEquals(1, workflowsWithContributor1.getTotalCount()); Assert.assertEquals(2, workflowsWithContributor2.getTotalCount()); Assert.assertEquals(1, workflowsWithContributor3.getTotalCount()); } @Test public void testGetWorkflowByWildcardMatching() throws Exception { String searchTerm = "another"; String searchTermWithoutQuotes = "yet another"; String searchTermInQuotes = "\"" + searchTermWithoutQuotes + "\""; String title = "just" + searchTerm + " " + searchTermInQuotes + " rev129"; // Ensure that the database doesn't have any workflow instances Assert.assertEquals(0, service.countWorkflowInstances()); mediapackage1.setTitle(title); startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowSet workflowsWithTitle = service.getWorkflowInstances(new WorkflowQuery().withTitle(searchTerm)); Assert.assertEquals(1, workflowsWithTitle.getTotalCount()); WorkflowSet workflowsWithQuotedTitle = service.getWorkflowInstances(new WorkflowQuery() .withTitle(searchTermInQuotes)); Assert.assertEquals(1, workflowsWithQuotedTitle.getTotalCount()); WorkflowSet workflowsWithUnQuotedTitle = service.getWorkflowInstances(new WorkflowQuery() .withTitle(searchTermWithoutQuotes)); Assert.assertEquals(1, workflowsWithUnQuotedTitle.getTotalCount()); } @Test public void testNegativeWorkflowQuery() throws Exception { // Ensure that the database doesn't have any workflow instances Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals(0, service.getWorkflowInstances(new WorkflowQuery().withText("Climate").withCount(100).withStartPage(0)) .size()); startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); startAndWait(failingDefinitionWithoutErrorHandler, mediapackage1, WorkflowState.FAILED); WorkflowSet succeededWorkflows = service.getWorkflowInstances(new WorkflowQuery() .withState(WorkflowState.SUCCEEDED)); Assert.assertEquals(2, succeededWorkflows.getItems().length); WorkflowSet failedWorkflows = service.getWorkflowInstances(new WorkflowQuery().withState(WorkflowState.FAILED)); Assert.assertEquals(1, failedWorkflows.getItems().length); // Ensure that the "without" queries works WorkflowSet notFailedWorkflows = service.getWorkflowInstances(new WorkflowQuery() .withoutState(WorkflowState.FAILED)); Assert.assertEquals(2, notFailedWorkflows.getItems().length); } protected WorkflowInstance startAndWait(WorkflowDefinition definition, MediaPackage mp, WorkflowState stateToWaitFor) throws Exception { return startAndWait(definition, mp, null, stateToWaitFor); } protected WorkflowInstance startAndWait(WorkflowDefinition definition, MediaPackage mp, Long parentId, WorkflowState stateToWaitFor) throws Exception { WorkflowStateListener stateListener = new WorkflowStateListener(stateToWaitFor); service.addWorkflowListener(stateListener); WorkflowInstance instance = null; synchronized (stateListener) { if (parentId == null) { instance = service.start(definition, mp); } else { instance = service.start(definition, mp, parentId, null); } stateListener.wait(); } service.removeWorkflowListener(stateListener); return instance; } @Test public void testPagedGetWorkflowByText() throws Exception { // Ensure that the database doesn't have any workflow instances Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals(0, service.getWorkflowInstances(new WorkflowQuery().withText("Climate").withCount(100).withStartPage(0)) .size()); List<WorkflowInstance> instances = new ArrayList<WorkflowInstance>(); instances.add(startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED)); instances.add(startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED)); instances.add(startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED)); instances.add(startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED)); instances.add(startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED)); Assert.assertEquals(5, service.countWorkflowInstances()); Assert.assertEquals(5, service.getWorkflowInstances(new WorkflowQuery()).getItems().length); // We should get the first two workflows WorkflowSet firstTwoWorkflows = service.getWorkflowInstances(new WorkflowQuery().withText("Climate").withCount(2) .withStartPage(0)); Assert.assertEquals(2, firstTwoWorkflows.getItems().length); Assert.assertEquals(3, firstTwoWorkflows.getTotalCount()); // The total, non-paged number of results should be three // We should get the last workflow WorkflowSet lastWorkflow = service.getWorkflowInstances(new WorkflowQuery().withText("Climate").withCount(1) .withStartPage(2)); Assert.assertEquals(1, lastWorkflow.getItems().length); Assert.assertEquals(3, lastWorkflow.getTotalCount()); // The total, non-paged number of results should be three // We should get the first linguistics (mediapackage2) workflow WorkflowSet firstLinguisticsWorkflow = service.getWorkflowInstances(new WorkflowQuery().withText("Linguistics") .withCount(1).withStartPage(0)); Assert.assertEquals(1, firstLinguisticsWorkflow.getItems().length); Assert.assertEquals(2, firstLinguisticsWorkflow.getTotalCount()); // The total, non-paged number of results should // be two // We should get the second linguistics (mediapackage2) workflow WorkflowSet secondLinguisticsWorkflow = service.getWorkflowInstances(new WorkflowQuery().withText("Linguistics") .withCount(1).withStartPage(1)); Assert.assertEquals(1, secondLinguisticsWorkflow.getItems().length); Assert.assertEquals(2, secondLinguisticsWorkflow.getTotalCount()); // The total, non-paged number of results should // be two } @Test public void testGetAllWorkflowInstances() throws Exception { Assert.assertEquals(0, service.countWorkflowInstances()); Assert.assertEquals(0, service.getWorkflowInstances(new WorkflowQuery()).size()); startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); WorkflowSet workflowsInDb = service.getWorkflowInstances(new WorkflowQuery()); Assert.assertEquals(2, workflowsInDb.getItems().length); } @Test public void testFailingOperationWithErrorHandler() throws Exception { WorkflowInstance instance = startAndWait(failingDefinitionWithErrorHandler, mediapackage1, WorkflowState.FAILED); Assert.assertEquals(WorkflowState.FAILED, service.getWorkflowById(instance.getId()).getState()); // The second operation should have failed Assert.assertEquals(OperationState.FAILED, service.getWorkflowById(instance.getId()).getOperations().get(1) .getState()); // Load a fresh copy WorkflowInstance storedInstance = service.getWorkflowById(instance.getId()); // Make sure the error handler has been added Assert.assertEquals(4, storedInstance.getOperations().size()); Assert.assertEquals("op1", storedInstance.getOperations().get(0).getTemplate()); Assert.assertEquals("op3", storedInstance.getOperations().get(1).getTemplate()); Assert.assertEquals("op1", storedInstance.getOperations().get(2).getTemplate()); Assert.assertEquals("op2", storedInstance.getOperations().get(3).getTemplate()); } @Test public void testFailingOperationWithoutErrorHandler() throws Exception { WorkflowInstance instance = startAndWait(failingDefinitionWithoutErrorHandler, mediapackage1, WorkflowState.FAILED); Assert.assertEquals(WorkflowState.FAILED, service.getWorkflowById(instance.getId()).getState()); } @Test public void testRetryStrategyNone() throws Exception { WorkflowDefinitionImpl def = new WorkflowDefinitionImpl(); def.setId("workflow-definition-1"); def.setTitle("workflow-definition-1"); def.setDescription("workflow-definition-1"); def.setPublished(true); service.registerWorkflowDefinition(def); WorkflowOperationDefinitionImpl opDef = new WorkflowOperationDefinitionImpl("failOneTime", "fails once", null, true); def.add(opDef); MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); WorkflowInstance workflow = startAndWait(def, mp, WorkflowState.FAILED); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getState() == OperationState.FAILED); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getMaxAttempts() == 1); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getFailedAttempts() == 1); } @Test public void testRetryStrategyRetry() throws Exception { WorkflowDefinitionImpl def = new WorkflowDefinitionImpl(); def.setId("workflow-definition-1"); def.setTitle("workflow-definition-1"); def.setDescription("workflow-definition-1"); def.setPublished(true); service.registerWorkflowDefinition(def); WorkflowOperationDefinitionImpl opDef = new WorkflowOperationDefinitionImpl("failOneTime", "fails once", null, true); opDef.setRetryStrategy(RetryStrategy.RETRY); def.add(opDef); MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); WorkflowInstance workflow = startAndWait(def, mp, WorkflowState.SUCCEEDED); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getState() == OperationState.SUCCEEDED); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getMaxAttempts() == 2); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getFailedAttempts() == 1); } @Test public void testRetryStrategyHold() throws Exception { WorkflowDefinitionImpl def = new WorkflowDefinitionImpl(); def.setId("workflow-definition-1"); def.setTitle("workflow-definition-1"); def.setDescription("workflow-definition-1"); def.setPublished(true); service.registerWorkflowDefinition(def); WorkflowOperationDefinitionImpl opDef = new WorkflowOperationDefinitionImpl("failOneTime", "fails once", null, true); opDef.setRetryStrategy(RetryStrategy.HOLD); def.add(opDef); MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); WorkflowInstance workflow = startAndWait(def, mp, WorkflowState.PAUSED); WorkflowOperationInstance errorResolutionOperation = service.getWorkflowById(workflow.getId()).getOperations() .get(0); WorkflowOperationInstance failOneTimeOperation = service.getWorkflowById(workflow.getId()).getOperations().get(1); Assert.assertTrue(errorResolutionOperation.getTemplate().equals(WorkflowServiceImpl.ERROR_RESOLUTION_HANDLER_ID)); Assert.assertTrue(errorResolutionOperation.getState() == OperationState.PAUSED); Assert.assertTrue(errorResolutionOperation.getFailedAttempts() == 0); Assert.assertTrue(failOneTimeOperation.getState() == OperationState.RETRY); Assert.assertTrue(failOneTimeOperation.getMaxAttempts() == -1); Assert.assertTrue(failOneTimeOperation.getFailedAttempts() == 1); } @Test @Ignore public void testRetryStrategyFailover() throws Exception { WorkflowDefinitionImpl def = new WorkflowDefinitionImpl(); def.setId("workflow-definition-1"); def.setTitle("workflow-definition-1"); def.setDescription("workflow-definition-1"); def.setPublished(true); service.registerWorkflowDefinition(def); WorkflowOperationDefinitionImpl opDef = new WorkflowOperationDefinitionImpl("failOnHost", "fails on host", null, true); opDef.setRetryStrategy(RetryStrategy.RETRY); def.add(opDef); MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); WorkflowInstance workflow = startAndWait(def, mp, WorkflowState.SUCCEEDED); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getState() == OperationState.SUCCEEDED); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getMaxAttempts() == 2); Assert.assertTrue(service.getWorkflowById(workflow.getId()).getOperations().get(0).getFailedAttempts() == 1); } /** * Starts many concurrent workflows to test DB deadlock. * * @throws Exception */ @Test public void testManyConcurrentWorkflows() throws Exception { int count = 10; Assert.assertEquals(0, service.countWorkflowInstances()); List<WorkflowInstance> instances = new ArrayList<WorkflowInstance>(); WorkflowStateListener stateListener = new WorkflowStateListener(WorkflowState.SUCCEEDED, WorkflowState.FAILED); service.addWorkflowListener(stateListener); for (int i = 0; i < count; i++) { MediaPackage mp = i % 2 == 0 ? mediapackage1 : mediapackage2; mp.setIdentifier(new UUIDIdBuilderImpl().createNew()); instances.add(service.start(workingDefinition, mp, null)); } while (stateListener.countStateChanges() < count) { synchronized (stateListener) { stateListener.wait(); } } Assert.assertEquals(count, service.countWorkflowInstances()); Assert.assertEquals(count, stateListener.countStateChanges(WorkflowState.SUCCEEDED)); } private WorkflowInstanceImpl setupWorkflowInstanceImpl(long id, String operation, WorkflowState state, Date startDate) throws ConfigurationException, MediaPackageException, NotFoundException, ServiceRegistryException { Job job = new JobImpl(id); job = serviceRegistry.updateJob(job); MediaPackage mediapackage = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); mediapackage.setDate(startDate); mediapackage.setDuration(7200L); WorkflowOperationInstanceImpl workflowOperation = new WorkflowOperationInstanceImpl(operation, OperationState.PAUSED); workflowOperation.setId(id); List<WorkflowOperationInstance> workflowOperationInstanceList = new LinkedList<WorkflowOperationInstance>(); workflowOperationInstanceList.add(workflowOperation); WorkflowInstanceImpl workflowInstanceImpl = new WorkflowInstanceImpl(); workflowInstanceImpl.setMediaPackage(mediapackage); workflowInstanceImpl.setState(state); workflowInstanceImpl.setId(id); workflowInstanceImpl.setOperations(workflowOperationInstanceList); return workflowInstanceImpl; } private void setupJob(long id, String operation, ServiceRegistry mockServiceRegistry) throws ServiceRegistryException, NotFoundException { ServiceRegistrationJpaImpl serviceRegistrationJpaImpl = EasyMock.createMock(ServiceRegistrationJpaImpl.class); expect(serviceRegistrationJpaImpl.getHost()).andReturn("http://localhost:8080"); expect(serviceRegistrationJpaImpl.getServiceType()).andReturn(operation); replay(serviceRegistrationJpaImpl); List<String> arguments = new LinkedList<String>(); arguments.add(Long.toString(id)); Job job = createNiceMock(Job.class); expect(job.getId()).andStubReturn(id); expect(job.getCreator()).andStubReturn(securityService.getUser().getUsername()); expect(job.getOrganization()).andStubReturn(securityService.getOrganization().getId()); expect(job.getOperation()).andStubReturn("RESUME"); expect(job.getArguments()).andStubReturn(arguments); expect(job.isDispatchable()).andStubReturn(false); expect(job.getStatus()).andStubReturn(Job.Status.INSTANTIATED); replay(job); expect(mockServiceRegistry.getJob(id)).andReturn(job).anyTimes(); expect(mockServiceRegistry.updateJob(job)).andReturn(job); } private void setupExceptionJob(long id, Exception e, ServiceRegistry mockServiceRegistry) throws ServiceRegistryException, NotFoundException { expect(mockServiceRegistry.getJob(id)).andThrow(e).anyTimes(); } private OrganizationDirectoryService setupMockOrganizationDirectoryService() { List<Organization> organizations = new LinkedList<Organization>(); organizations.add(securityService.getOrganization()); OrganizationDirectoryService orgDirService = EasyMock.createMock(OrganizationDirectoryService.class); expect(orgDirService.getOrganizations()).andReturn(organizations).anyTimes(); replay(orgDirService); return orgDirService; } /** * Test for {@link WorkflowServiceImpl#remove(long)} * * @throws Exception * if anything fails */ @Test public void testRemove() throws Exception { WorkflowInstance wi1 = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); WorkflowInstance wi2 = startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); // reload instances, because operations have no id before wi1 = service.getWorkflowById(wi1.getId()); wi2 = service.getWorkflowById(wi2.getId()); service.remove(wi1.getId()); assertEquals(1, service.getWorkflowInstances(new WorkflowQuery()).size()); for (WorkflowOperationInstance op : wi1.getOperations()) { assertEquals(0, serviceRegistry.getChildJobs(op.getId()).size()); } service.remove(wi2.getId()); assertEquals(0, service.getWorkflowInstances(new WorkflowQuery()).size()); } /** * Test for {@link WorkflowServiceImpl#cleanupWorkflowInstances(int, WorkflowState)} * * @throws Exception * if anything fails */ @Test public void testCleanupWorkflowInstances() throws Exception { WorkflowInstance wi1 = startAndWait(workingDefinition, mediapackage1, WorkflowState.SUCCEEDED); startAndWait(workingDefinition, mediapackage2, WorkflowState.SUCCEEDED); // reload instances, because operations have no id before wi1 = service.getWorkflowById(wi1.getId()); service.cleanupWorkflowInstances(0, WorkflowState.FAILED); assertEquals(2, service.getWorkflowInstances(new WorkflowQuery()).size()); service.cleanupWorkflowInstances(0, WorkflowState.SUCCEEDED); assertEquals(0, service.getWorkflowInstances(new WorkflowQuery()).size()); for (WorkflowOperationInstance op : wi1.getOperations()) { assertEquals(0, serviceRegistry.getChildJobs(op.getId()).size()); } } class SucceedingWorkflowOperationHandler extends AbstractWorkflowOperationHandler { @Override public SortedMap<String, String> getConfigurationOptions() { return new TreeMap<String, String>(); } @Override public String getId() { return this.getClass().getName(); } @Override public String getDescription() { return "ContinuingWorkflowOperationHandler"; } @Override public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException { return createResult(workflowInstance.getMediaPackage(), Action.CONTINUE); } } class FailingWorkflowOperationHandler extends AbstractWorkflowOperationHandler { @Override public SortedMap<String, String> getConfigurationOptions() { return new TreeMap<String, String>(); } @Override public String getId() { return this.getClass().getName(); } @Override public String getDescription() { return "ContinuingWorkflowOperationHandler"; } @Override public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException { throw new WorkflowOperationException("this operation handler always fails. that's the point."); } } class FailOnceWorkflowOperationHandler extends AbstractWorkflowOperationHandler { /** Whether this handler has been run yet */ private boolean hasRun = false; /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.AbstractWorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance, * org.opencastproject.job.api.JobContext) */ @Override public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException { if (!hasRun) { hasRun = true; throw new WorkflowOperationException("This operation handler fails on the first run, but succeeds thereafter"); } return createResult(CONTINUE); } } class FailOnHostWorkflowOperationHandler extends AbstractWorkflowOperationHandler { private String oldExecutionHost = null; /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.AbstractWorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance, * org.opencastproject.job.api.JobContext) */ @Override public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext jobContext) throws WorkflowOperationException { if (oldExecutionHost == null) { oldExecutionHost = workflowInstance.getCurrentOperation().getExecutionHost(); throw new WorkflowOperationException("This operation handler fails on the first run on host: " + oldExecutionHost); } if (workflowInstance.getCurrentOperation().getExecutionHost().equals(oldExecutionHost)) throw new WorkflowOperationException("This operation handler fails on the second run at the same host: " + oldExecutionHost); return createResult(CONTINUE); } } }