org.apache.nifi.controller.scheduling.TestProcessorLifecycle.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.controller.scheduling.TestProcessorLifecycle.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache 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://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.apache.nifi.controller.scheduling;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import org.apache.commons.io.FileUtils;
import org.apache.nifi.admin.service.AuditService;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ProcessScheduler;
import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.repository.FlowFileEventRepository;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.events.VolatileBulletinRepository;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.provenance.MockProvenanceRepository;
import org.apache.nifi.util.FileBasedVariableRegistry;
import org.apache.nifi.util.NiFiProperties;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Validate Processor's life-cycle operation within the context of
 * {@link FlowController} and {@link StandardProcessScheduler}
 */
public class TestProcessorLifecycle {

    private static final Logger logger = LoggerFactory.getLogger(TestProcessorLifecycle.class);
    private FlowController fc;
    private Map<String, String> properties = new HashMap<>();

    @Before
    public void before() throws Exception {
        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH,
                TestProcessorLifecycle.class.getResource("/nifi.properties").getFile());
        properties.put("P", "hello");
    }

    @After
    public void after() throws Exception {
        fc.shutdown(true);
        FileUtils.deleteDirectory(new File("./target/test-repo"));
        FileUtils.deleteDirectory(new File("./target/content_repository"));
    }

    @Test
    public void validateEnableOperation() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        final ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());

        assertEquals(ScheduledState.STOPPED, testProcNode.getScheduledState());
        assertEquals(ScheduledState.STOPPED, testProcNode.getPhysicalScheduledState());
        // validates idempotency
        for (int i = 0; i < 2; i++) {
            testProcNode.enable();
        }
        assertEquals(ScheduledState.STOPPED, testProcNode.getScheduledState());
        assertEquals(ScheduledState.STOPPED, testProcNode.getPhysicalScheduledState());
        testProcNode.disable();
        assertEquals(ScheduledState.DISABLED, testProcNode.getScheduledState());
        assertEquals(ScheduledState.DISABLED, testProcNode.getPhysicalScheduledState());
    }

    @Test
    public void validateDisableOperation() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        final ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        assertEquals(ScheduledState.STOPPED, testProcNode.getScheduledState());
        assertEquals(ScheduledState.STOPPED, testProcNode.getPhysicalScheduledState());
        // validates idempotency
        for (int i = 0; i < 2; i++) {
            testProcNode.disable();
        }
        assertEquals(ScheduledState.DISABLED, testProcNode.getScheduledState());
        assertEquals(ScheduledState.DISABLED, testProcNode.getPhysicalScheduledState());

        ProcessScheduler ps = fc.getProcessScheduler();
        ps.startProcessor(testProcNode);
        assertEquals(ScheduledState.DISABLED, testProcNode.getPhysicalScheduledState());
    }

    /**
     * Will validate the idempotent nature of processor start operation which
     * can be called multiple times without any side-effects.
     */
    @Test
    public void validateIdempotencyOfProcessorStartOperation() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        final ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        this.noop(testProcessor);
        final ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        ps.startProcessor(testProcNode);
        ps.startProcessor(testProcNode);

        Thread.sleep(500);
        assertEquals(1, testProcessor.operationNames.size());
        assertEquals("@OnScheduled", testProcessor.operationNames.get(0));
    }

    /**
     * Validates that stop calls are harmless and idempotent if processor is not
     * in STARTING or RUNNING state.
     */
    @Test
    public void validateStopCallsAreMeaninglessIfProcessorNotStarted() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        final ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
        // sets the scenario for the processor to run
        int randomDelayLimit = 3000;
        this.randomOnTriggerDelay(testProcessor, randomDelayLimit);
        final ProcessScheduler ps = fc.getProcessScheduler();
        ps.stopProcessor(testProcNode);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
        assertTrue(testProcessor.operationNames.size() == 0);
    }

    /**
     * Validates the processors start/stop sequence where the order of
     * operations can only be @OnScheduled, @OnUnscheduled, @OnStopped.
     */
    @Test
    @Ignore
    public void validateSuccessfullAndOrderlyShutdown() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        int randomDelayLimit = 3000;
        this.randomOnTriggerDelay(testProcessor, randomDelayLimit);

        testProcNode.setMaxConcurrentTasks(4);
        testProcNode.setScheduldingPeriod("500 millis");
        testProcNode.setAutoTerminatedRelationships(
                Collections.singleton(new Relationship.Builder().name("success").build()));

        testGroup.addProcessor(testProcNode);

        fc.startProcessGroup(testGroup.getIdentifier());
        Thread.sleep(2000); // let it run for a while
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);

        fc.stopAllProcessors();

        Thread.sleep(randomDelayLimit); // up to randomDelayLimit, otherwise next assertion may fail as the processor still executing

        // validates that regardless of how many running tasks, lifecycle
        // operation are invoked atomically (once each).
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
        // . . . hence only 3 operations must be in the list
        assertEquals(3, testProcessor.operationNames.size());
        // . . . and ordered as @OnScheduled, @OnUnscheduled, @OnStopped
        assertEquals("@OnScheduled", testProcessor.operationNames.get(0));
        assertEquals("@OnUnscheduled", testProcessor.operationNames.get(1));
        assertEquals("@OnStopped", testProcessor.operationNames.get(2));
    }

    /**
     * Concurrency test that is basically hammers on both stop and start
     * operation validating their idempotency.
     */
    @Test
    @Ignore
    public void validateLifecycleOperationOrderWithConcurrentCallsToStartStop() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        final ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        this.noop(testProcessor);

        final ProcessScheduler ps = fc.getProcessScheduler();
        ExecutorService executor = Executors.newFixedThreadPool(100);
        int startCallsCount = 10000;
        final CountDownLatch countDownCounter = new CountDownLatch(startCallsCount);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
        final Random random = new Random();
        for (int i = 0; i < startCallsCount / 2; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    LockSupport.parkNanos(random.nextInt(9000000));
                    ps.stopProcessor(testProcNode);
                    countDownCounter.countDown();
                }
            });
        }
        for (int i = 0; i < startCallsCount / 2; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    LockSupport.parkNanos(random.nextInt(9000000));
                    ps.startProcessor(testProcNode);
                    countDownCounter.countDown();
                }
            });
        }
        assertTrue(countDownCounter.await(1000000, TimeUnit.MILLISECONDS));
        String previousOperation = null;
        for (String operationName : testProcessor.operationNames) {
            if (previousOperation == null || previousOperation.equals("@OnStopped")) {
                assertEquals("@OnScheduled", operationName);
            } else if (previousOperation.equals("@OnScheduled")) {
                assertEquals("@OnUnscheduled", operationName);
            } else if (previousOperation.equals("@OnUnscheduled")) {
                assertTrue(operationName.equals("@OnStopped") || operationName.equals("@OnScheduled"));
            }
            previousOperation = operationName;
        }
        executor.shutdownNow();
    }

    /**
     * Validates that processor can be stopped before start sequence finished.
     */
    @Test
    public void validateProcessorUnscheduledAndStoppedWhenStopIsCalledBeforeProcessorFullyStarted()
            throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        int delay = 2000;
        this.longRunningOnSchedule(testProcessor, delay);
        ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);

        ps.stopProcessor(testProcNode);
        Thread.sleep(100);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STOPPING);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
        Thread.sleep(1000);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);

        assertEquals(2, testProcessor.operationNames.size());
        assertEquals("@OnScheduled", testProcessor.operationNames.get(0));
        assertEquals("@OnUnscheduled", testProcessor.operationNames.get(1));
    }

    /**
     * Validates that Processor is eventually started once invocation of
     *
     * @OnSchedule stopped throwing exceptions.
     */
    @Test
    public void validateProcessScheduledAfterAdministrativeDelayDueToTheOnScheduledException() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        this.noop(testProcessor);
        testProcessor.generateExceptionOnScheduled = true;
        testProcessor.keepFailingOnScheduledTimes = 2;
        ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        Thread.sleep(100);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);
        ps.stopProcessor(testProcNode);
        Thread.sleep(500);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
    }

    /**
     * Validates that Processor can be stopped when @OnScheduled constantly
     * fails. Basically validates that the re-try loop breaks if user initiated
     * stopProcessor.
     */
    @Test
    public void validateProcessorCanBeStoppedWhenOnScheduledConstantlyFails() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        this.longRunningOnUnschedule(testProcessor, 100);
        testProcessor.generateExceptionOnScheduled = true;
        testProcessor.keepFailingOnScheduledTimes = Integer.MAX_VALUE;
        ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        ps.stopProcessor(testProcNode);
        Thread.sleep(100);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STOPPING);
        Thread.sleep(500);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
    }

    /**
     * Validates that the Processor can be stopped when @OnScheduled blocks
     * indefinitely but written to react to thread interrupts
     */
    @Test
    public void validateProcessorCanBeStoppedWhenOnScheduledBlocksIndefinitelyInterruptable() throws Exception {
        this.fc = buildFlowControllerForTest(NiFiProperties.PROCESSOR_SCHEDULING_TIMEOUT, "5 sec");
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();
        // sets the scenario for the processor to run
        this.blockingInterruptableOnUnschedule(testProcessor);
        ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        ps.stopProcessor(testProcNode);
        Thread.sleep(100);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STOPPING);
        Thread.sleep(4000);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
    }

    /**
     * Validates that the Processor can be stopped when @OnScheduled blocks
     * indefinitely and written to ignore thread interrupts
     */
    @Test
    public void validateProcessorCanBeStoppedWhenOnScheduledBlocksIndefinitelyUninterruptable() throws Exception {
        this.fc = buildFlowControllerForTest(NiFiProperties.PROCESSOR_SCHEDULING_TIMEOUT, "5 sec");
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();
        // sets the scenario for the processor to run
        this.blockingUninterruptableOnUnschedule(testProcessor);
        ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        Thread.sleep(1000);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        ps.disableProcessor(testProcNode); // no effect
        Thread.sleep(100);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STARTING);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);
        ps.stopProcessor(testProcNode);
        Thread.sleep(100);
        assertTrue(testProcNode.getPhysicalScheduledState() == ScheduledState.STOPPING);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
        Thread.sleep(4000);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
    }

    /**
     * Validates that processor can be stopped if onTrigger() keeps trowing
     * exceptions.
     */
    @Test
    public void validateProcessorCanBeStoppedWhenOnTriggerThrowsException() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNode.setProperties(properties);
        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();

        // sets the scenario for the processor to run
        this.noop(testProcessor);
        testProcessor.generateExceptionOnTrigger = true;
        ProcessScheduler ps = fc.getProcessScheduler();

        ps.startProcessor(testProcNode);
        Thread.sleep(1000);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);
        ps.disableProcessor(testProcNode);
        Thread.sleep(100);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);
        ps.stopProcessor(testProcNode);
        Thread.sleep(500);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.STOPPED);
    }

    /**
     * Validate that processor will not be validated on failing
     * PropertyDescriptor validation.
     */
    @Test(expected = IllegalStateException.class)
    public void validateStartFailsOnInvalidProcessorWithMissingProperty() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        ProcessScheduler ps = fc.getProcessScheduler();
        ps.startProcessor(testProcNode);
        fail();
    }

    /**
     * Validate that processor will not be validated on failing
     * ControllerService validation (not enabled).
     */
    @Test(expected = IllegalStateException.class)
    public void validateStartFailsOnInvalidProcessorWithDisabledService() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);

        ControllerServiceNode testServiceNode = fc.createControllerService(TestService.class.getName(), "serv",
                true);
        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());

        properties.put("S", testServiceNode.getIdentifier());
        testProcNode.setProperties(properties);

        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();
        testProcessor.withService = true;

        ProcessScheduler ps = fc.getProcessScheduler();
        ps.startProcessor(testProcNode);
        fail();
    }

    /**
     * The successful processor start with ControllerService dependency.
     */
    @Test
    public void validateStartSucceedsOnProcessorWithEnabledService() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);

        ControllerServiceNode testServiceNode = fc.createControllerService(TestService.class.getName(), "foo",
                true);
        testGroup.addControllerService(testServiceNode);

        ProcessorNode testProcNode = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testGroup.addProcessor(testProcNode);

        properties.put("S", testServiceNode.getIdentifier());
        testProcNode.setProperties(properties);

        TestProcessor testProcessor = (TestProcessor) testProcNode.getProcessor();
        testProcessor.withService = true;
        this.noop(testProcessor);

        ProcessScheduler ps = fc.getProcessScheduler();
        ps.enableControllerService(testServiceNode);
        ps.startProcessor(testProcNode);

        Thread.sleep(500);
        assertTrue(testProcNode.getScheduledState() == ScheduledState.RUNNING);
    }

    /**
     * Test deletion of processor when connected to another
     *
     * @throws Exception exception
     */
    @Test
    public void validateProcessorDeletion() throws Exception {
        fc = this.buildFlowControllerForTest();
        ProcessGroup testGroup = fc.createProcessGroup(UUID.randomUUID().toString());
        this.setControllerRootGroup(fc, testGroup);

        ProcessorNode testProcNodeA = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNodeA.setProperties(properties);
        testGroup.addProcessor(testProcNodeA);

        ProcessorNode testProcNodeB = fc.createProcessor(TestProcessor.class.getName(),
                UUID.randomUUID().toString());
        testProcNodeB.setProperties(properties);
        testGroup.addProcessor(testProcNodeB);

        Collection<String> relationNames = new ArrayList<String>();
        relationNames.add("relation");
        Connection connection = fc.createConnection(UUID.randomUUID().toString(), Connection.class.getName(),
                testProcNodeA, testProcNodeB, relationNames);
        testGroup.addConnection(connection);

        ProcessScheduler ps = fc.getProcessScheduler();
        ps.startProcessor(testProcNodeA);
        ps.startProcessor(testProcNodeB);

        try {
            testGroup.removeProcessor(testProcNodeA);
            fail();
        } catch (Exception e) {
            // should throw exception because processor running
        }

        try {
            testGroup.removeProcessor(testProcNodeB);
            fail();
        } catch (Exception e) {
            // should throw exception because processor running
        }

        ps.stopProcessor(testProcNodeB);
        Thread.sleep(100);

        try {
            testGroup.removeProcessor(testProcNodeA);
            fail();
        } catch (Exception e) {
            // should throw exception because destination processor running
        }

        try {
            testGroup.removeProcessor(testProcNodeB);
            fail();
        } catch (Exception e) {
            // should throw exception because source processor running
        }

        ps.stopProcessor(testProcNodeA);
        Thread.sleep(100);

        testGroup.removeProcessor(testProcNodeA);
        testGroup.removeProcessor(testProcNodeB);
        testGroup.shutdown();
    }

    /**
     * Scenario where onTrigger() is executed with random delay limited to
     * 'delayLimit', yet with guaranteed exit from onTrigger().
     */
    private void randomOnTriggerDelay(TestProcessor testProcessor, int delayLimit) {
        EmptyRunnable emptyRunnable = new EmptyRunnable();
        RandomOrFixedDelayedRunnable delayedRunnable = new RandomOrFixedDelayedRunnable(delayLimit, true);
        testProcessor.setScenario(emptyRunnable, emptyRunnable, emptyRunnable, delayedRunnable);
    }

    /**
     * Scenario where @OnSchedule is executed with delay limited to
     * 'delayLimit'.
     */
    private void longRunningOnSchedule(TestProcessor testProcessor, int delayLimit) {
        EmptyRunnable emptyRunnable = new EmptyRunnable();
        RandomOrFixedDelayedRunnable delayedRunnable = new RandomOrFixedDelayedRunnable(delayLimit, false);
        testProcessor.setScenario(delayedRunnable, emptyRunnable, emptyRunnable, emptyRunnable);
    }

    /**
     * Scenario where @OnUnschedule is executed with delay limited to
     * 'delayLimit'.
     */
    private void longRunningOnUnschedule(TestProcessor testProcessor, int delayLimit) {
        EmptyRunnable emptyRunnable = new EmptyRunnable();
        RandomOrFixedDelayedRunnable delayedRunnable = new RandomOrFixedDelayedRunnable(delayLimit, false);
        testProcessor.setScenario(emptyRunnable, delayedRunnable, emptyRunnable, emptyRunnable);
    }

    /**
     * Scenario where @OnSchedule blocks indefinitely yet interruptible.
     */
    private void blockingInterruptableOnUnschedule(TestProcessor testProcessor) {
        EmptyRunnable emptyRunnable = new EmptyRunnable();
        BlockingInterruptableRunnable blockingRunnable = new BlockingInterruptableRunnable();
        testProcessor.setScenario(blockingRunnable, emptyRunnable, emptyRunnable, emptyRunnable);
    }

    /**
     * Scenario where @OnSchedule blocks indefinitely and un-interruptible.
     */
    private void blockingUninterruptableOnUnschedule(TestProcessor testProcessor) {
        EmptyRunnable emptyRunnable = new EmptyRunnable();
        BlockingUninterruptableRunnable blockingRunnable = new BlockingUninterruptableRunnable();
        testProcessor.setScenario(blockingRunnable, emptyRunnable, emptyRunnable, emptyRunnable);
    }

    /**
     * Scenario where all tasks are no op.
     */
    private void noop(TestProcessor testProcessor) {
        EmptyRunnable emptyRunnable = new EmptyRunnable();
        testProcessor.setScenario(emptyRunnable, emptyRunnable, emptyRunnable, emptyRunnable);
    }

    private FlowController buildFlowControllerForTest(final String propKey, final String propValue)
            throws Exception {
        final Map<String, String> addProps = new HashMap<>();
        addProps.put(NiFiProperties.ADMINISTRATIVE_YIELD_DURATION, "1 sec");
        addProps.put(NiFiProperties.STATE_MANAGEMENT_CONFIG_FILE, "target/test-classes/state-management.xml");
        addProps.put(NiFiProperties.STATE_MANAGEMENT_LOCAL_PROVIDER_ID, "local-provider");
        addProps.put(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS, MockProvenanceRepository.class.getName());
        addProps.put("nifi.remote.input.socket.port", "");
        addProps.put("nifi.remote.input.secure", "");
        if (propKey != null && propValue != null) {
            addProps.put(propKey, propValue);
        }
        final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
        return FlowController.createStandaloneInstance(mock(FlowFileEventRepository.class), nifiProperties,
                mock(Authorizer.class), mock(AuditService.class), null, new VolatileBulletinRepository(),
                new FileBasedVariableRegistry(nifiProperties.getVariableRegistryPropertiesPaths()));
    }

    private FlowController buildFlowControllerForTest() throws Exception {
        return buildFlowControllerForTest(null, null);
    }

    /**
     *
     */
    private void setControllerRootGroup(FlowController controller, ProcessGroup processGroup) {
        try {
            Method m = FlowController.class.getDeclaredMethod("setRootGroup", ProcessGroup.class);
            m.setAccessible(true);
            m.invoke(controller, processGroup);
            controller.initializeFlow();
        } catch (Exception e) {
            throw new IllegalStateException("Failed to set root group", e);
        }
    }

    /**
     */
    public static class TestProcessor extends AbstractProcessor {

        private Runnable onScheduleCallback;
        private Runnable onUnscheduleCallback;
        private Runnable onStopCallback;
        private Runnable onTriggerCallback;

        private boolean generateExceptionOnScheduled;
        private boolean generateExceptionOnTrigger;

        private boolean withService;

        private int keepFailingOnScheduledTimes;

        private int onScheduledExceptionCount;

        private final List<String> operationNames = new LinkedList<>();

        void setScenario(Runnable onScheduleCallback, Runnable onUnscheduleCallback, Runnable onStopCallback,
                Runnable onTriggerCallback) {
            this.onScheduleCallback = onScheduleCallback;
            this.onUnscheduleCallback = onUnscheduleCallback;
            this.onStopCallback = onStopCallback;
            this.onTriggerCallback = onTriggerCallback;
        }

        @OnScheduled
        public void schedule(ProcessContext ctx) {
            this.operationNames.add("@OnScheduled");
            if (this.generateExceptionOnScheduled
                    && this.onScheduledExceptionCount++ < this.keepFailingOnScheduledTimes) {
                throw new RuntimeException("Intentional");
            }
            this.onScheduleCallback.run();
        }

        @OnUnscheduled
        public void unschedule() {
            this.operationNames.add("@OnUnscheduled");
            this.onUnscheduleCallback.run();
        }

        @OnStopped
        public void stop() {
            this.operationNames.add("@OnStopped");
            this.onStopCallback.run();
        }

        @Override
        protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
            PropertyDescriptor PROP = new PropertyDescriptor.Builder().name("P").description("Blah Blah")
                    .required(true).addValidator(new Validator() {
                        @Override
                        public ValidationResult validate(final String subject, final String value,
                                final ValidationContext context) {
                            return new ValidationResult.Builder().subject(subject).input(value)
                                    .valid(value != null && !value.isEmpty())
                                    .explanation(subject + " cannot be empty").build();
                        }
                    }).build();

            PropertyDescriptor SERVICE = new PropertyDescriptor.Builder().name("S").description("Blah Blah")
                    .required(true).identifiesControllerService(ITestservice.class).build();

            return this.withService ? Arrays.asList(new PropertyDescriptor[] { PROP, SERVICE })
                    : Arrays.asList(new PropertyDescriptor[] { PROP });
        }

        @Override
        public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException {
            if (this.generateExceptionOnTrigger) {
                throw new RuntimeException("Intentional");
            }
            this.onTriggerCallback.run();
        }
    }

    /**
     */
    public static class TestService extends AbstractControllerService implements ITestservice {

    }

    /**
     */
    public static interface ITestservice extends ControllerService {

    }

    /**
     */
    private static class EmptyRunnable implements Runnable {

        @Override
        public void run() {

        }
    }

    /**
     */
    private static class BlockingInterruptableRunnable implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     */
    private static class BlockingUninterruptableRunnable implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }
    }

    /**
     */
    private static class RandomOrFixedDelayedRunnable implements Runnable {

        private final int delayLimit;
        private final boolean randomDelay;

        public RandomOrFixedDelayedRunnable(int delayLimit, boolean randomDelay) {
            this.delayLimit = delayLimit;
            this.randomDelay = randomDelay;
        }

        Random random = new Random();

        @Override
        public void run() {
            try {
                if (this.randomDelay) {
                    Thread.sleep(random.nextInt(this.delayLimit));
                } else {
                    Thread.sleep(this.delayLimit);
                }
            } catch (InterruptedException e) {
                logger.warn("Interrupted while sleeping");
                Thread.currentThread().interrupt();
            }
        }
    }
}