Package com.asakusafw.yaess.core.task

Source Code of com.asakusafw.yaess.core.task.ExecutionTask$FlowScriptTask

/**
* Copyright 2011-2014 Asakusa Framework Team.
*
* Licensed 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 com.asakusafw.yaess.core.task;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.runtime.core.context.RuntimeContext;
import com.asakusafw.runtime.core.context.RuntimeContext.ExecutionMode;
import com.asakusafw.yaess.basic.ExitCodeException;
import com.asakusafw.yaess.core.BatchScript;
import com.asakusafw.yaess.core.CommandScript;
import com.asakusafw.yaess.core.CommandScriptHandler;
import com.asakusafw.yaess.core.ExecutionContext;
import com.asakusafw.yaess.core.ExecutionLock;
import com.asakusafw.yaess.core.ExecutionLockProvider;
import com.asakusafw.yaess.core.ExecutionMonitorProvider;
import com.asakusafw.yaess.core.ExecutionPhase;
import com.asakusafw.yaess.core.ExecutionScript;
import com.asakusafw.yaess.core.FlowScript;
import com.asakusafw.yaess.core.HadoopScript;
import com.asakusafw.yaess.core.HadoopScriptHandler;
import com.asakusafw.yaess.core.Job;
import com.asakusafw.yaess.core.JobScheduler;
import com.asakusafw.yaess.core.JobScheduler.ErrorHandler;
import com.asakusafw.yaess.core.PhaseMonitor;
import com.asakusafw.yaess.core.ServiceProfile;
import com.asakusafw.yaess.core.YaessCoreLogger;
import com.asakusafw.yaess.core.YaessLogger;
import com.asakusafw.yaess.core.YaessProfile;

/**
* Task to execute target batch, flow, or phase.
* @since 0.2.3
* @version 0.4.0
*/
public class ExecutionTask {

    static final String KEY_SKIP_FLOWS = "skipFlows";

    static final String KEY_SERIALIZE_FLOWS = "serializeFlows";

    static final String KEY_VERIFY_APPLICATION = "verifyApplication";

    static final String KEY_VERIFY_DRYRUN = "dryRun";

    static final YaessLogger YSLOG = new YaessCoreLogger(ExecutionTask.class);

    static final Logger LOG = LoggerFactory.getLogger(ExecutionTask.class);

    private final ExecutionMonitorProvider monitors;

    private final ExecutionLockProvider locks;

    private final JobScheduler scheduler;

    private final HadoopScriptHandler hadoopHandler;

    private final Map<String, CommandScriptHandler> commandHandlers;

    private final BatchScript script;

    private final Map<String, String> batchArguments;

    private final Map<String, String> subprocessEnvironmentVaritables = new ConcurrentHashMap<String, String>();

    private final Set<String> skipFlows = Collections.synchronizedSet(new HashSet<String>());

    private volatile RuntimeContext runtimeContext;

    private volatile boolean serializeFlows = false;

    /**
     * Creates a new instance.
     * @param monitors monitor provider
     * @param locks lock provider
     * @param scheduler job scheduler
     * @param hadoopHandler Hadoop script handler
     * @param commandHandlers command script handlers and its profile names
     * @param script target script
     * @param batchArguments current batch arguments
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public ExecutionTask(
            ExecutionMonitorProvider monitors,
            ExecutionLockProvider locks,
            JobScheduler scheduler,
            HadoopScriptHandler hadoopHandler,
            Map<String, CommandScriptHandler> commandHandlers,
            BatchScript script,
            Map<String, String> batchArguments) {
        if (monitors == null) {
            throw new IllegalArgumentException("monitors must not be null"); //$NON-NLS-1$
        }
        if (locks == null) {
            throw new IllegalArgumentException("locks must not be null"); //$NON-NLS-1$
        }
        if (scheduler == null) {
            throw new IllegalArgumentException("scheduler must not be null"); //$NON-NLS-1$
        }
        if (hadoopHandler == null) {
            throw new IllegalArgumentException("hadoopHandler must not be null"); //$NON-NLS-1$
        }
        if (commandHandlers == null) {
            throw new IllegalArgumentException("commandHandlers must not be null"); //$NON-NLS-1$
        }
        if (script == null) {
            throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
        }
        if (batchArguments == null) {
            throw new IllegalArgumentException("batchArguments must not be null"); //$NON-NLS-1$
        }
        this.monitors = monitors;
        this.locks = locks;
        this.scheduler = scheduler;
        this.hadoopHandler = hadoopHandler;
        this.commandHandlers = Collections.unmodifiableMap(new HashMap<String, CommandScriptHandler>(commandHandlers));
        this.script = script;
        this.batchArguments = Collections.unmodifiableMap(new LinkedHashMap<String, String>(batchArguments));
        this.runtimeContext = RuntimeContext.get();
    }

    /**
     * Loads profile and create a new {@link ExecutionTask}.
     * @param profile target profile
     * @param script target script
     * @param batchArguments current batch arguments
     * @return the created task
     * @throws InterruptedException if interrupted while configuring services
     * @throws IOException if failed to configure services
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public static ExecutionTask load(
            YaessProfile profile,
            Properties script,
            Map<String, String> batchArguments) throws InterruptedException, IOException {
        return load(profile, script, batchArguments, Collections.<String, String>emptyMap());
    }

    /**
     * Loads profile and create a new {@link ExecutionTask}.
     * @param profile target profile
     * @param script target script
     * @param batchArguments current batch arguments
     * @param yaessArguments current controll arguments
     * @return the created task
     * @throws InterruptedException if interrupted while configuring services
     * @throws IOException if failed to configure services
     * @throws IllegalArgumentException if some parameters were {@code null}
     * @since 0.2.6
     */
    public static ExecutionTask load(
            YaessProfile profile,
            Properties script,
            Map<String, String> batchArguments,
            Map<String, String> yaessArguments) throws InterruptedException, IOException {
        if (profile == null) {
            throw new IllegalArgumentException("profile must not be null"); //$NON-NLS-1$
        }
        if (script == null) {
            throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
        }
        if (batchArguments == null) {
            throw new IllegalArgumentException("batchArguments must not be null"); //$NON-NLS-1$
        }
        if (yaessArguments == null) {
            throw new IllegalArgumentException("yaessArguments must not be null"); //$NON-NLS-1$
        }
        LOG.debug("Loading execution monitor feature");
        ExecutionMonitorProvider monitors = profile.getMonitors().newInstance();

        LOG.debug("Loading execution lock feature");
        ExecutionLockProvider locks = profile.getLocks().newInstance();

        LOG.debug("Loading job scheduling feature");
        JobScheduler scheduler = profile.getScheduler().newInstance();

        LOG.debug("Loading hadoop execution feature");
        HadoopScriptHandler hadoopHandler = profile.getHadoopHandler().newInstance();

        LOG.debug("Loading command execution features");
        Map<String, CommandScriptHandler> commandHandlers = new HashMap<String, CommandScriptHandler>();
        for (Map.Entry<String, ServiceProfile<CommandScriptHandler>> entry
                : profile.getCommandHandlers().entrySet()) {
            commandHandlers.put(entry.getKey(), entry.getValue().newInstance());
        }

        LOG.debug("Extracting batch script");
        BatchScript batch = BatchScript.load(script);

        ExecutionTask result = new ExecutionTask(
                monitors,
                locks,
                scheduler,
                hadoopHandler,
                commandHandlers,
                batch,
                batchArguments);

        LOG.debug("Applying definitions");
        Map<String, String> copyDefinitions = new TreeMap<String, String>(yaessArguments);
        consumeRuntimeContext(result, copyDefinitions, batch);
        consumeSkipFlows(result, copyDefinitions, batch);
        consumeSerializeFlows(result, copyDefinitions, batch);
        checkRest(copyDefinitions);

        return result;
    }

    private static void consumeRuntimeContext(
            ExecutionTask result,
            Map<String, String> copyDefinitions,
            BatchScript script) {
        assert result != null;
        assert copyDefinitions != null;
        assert script != null;
        RuntimeContext rc = RuntimeContext.get().batchId(script.getId()).buildId(script.getBuildId());

        Ternary dryRunResult = consumeBoolean(copyDefinitions, KEY_VERIFY_DRYRUN);
        if (dryRunResult == Ternary.TRUE) {
            rc = rc.mode(ExecutionMode.SIMULATION);
        } else if (dryRunResult == Ternary.FALSE) {
            rc = rc.mode(ExecutionMode.PRODUCTION);
        }

        Ternary verify = consumeBoolean(copyDefinitions, KEY_VERIFY_APPLICATION);
        if (verify == Ternary.FALSE) {
            rc = rc.buildId(null);
        }

        result.runtimeContext = rc;
        result.getEnv().putAll(rc.unapply());
    }

    private static void consumeSkipFlows(
            ExecutionTask task,
            Map<String, String> copyDefinitions,
            BatchScript script) {
        assert task != null;
        assert copyDefinitions != null;
        assert script != null;
        String flows = copyDefinitions.remove(KEY_SKIP_FLOWS);
        if (flows == null || flows.trim().isEmpty()) {
            return;
        }
        LOG.debug("Definition: {}={}", KEY_SKIP_FLOWS, flows);
        for (String flowIdCandidate : flows.split(",")) {
            String flowId = flowIdCandidate.trim();
            if (flowId.isEmpty() == false) {
                FlowScript flow = script.findFlow(flowId);
                if (flow == null) {
                    throw new IllegalArgumentException(MessageFormat.format(
                            "Unknown flowId in definition {0} : {1}",
                            KEY_SKIP_FLOWS,
                            flowId));
                }
                task.skipFlows.add(flowId);
            }
        }
    }

    private static void consumeSerializeFlows(
            ExecutionTask task,
            Map<String, String> copyDefinitions,
            BatchScript script) {
        assert task != null;
        assert copyDefinitions != null;
        assert script != null;

        Ternary serialize = consumeBoolean(copyDefinitions, KEY_SERIALIZE_FLOWS);
        if (serialize == null) {
            return;
        }
        task.serializeFlows = serialize == Ternary.TRUE;
    }

    private static Ternary consumeBoolean(Map<String, String> copyDefinitions, String key) {
        assert copyDefinitions != null;
        String value = copyDefinitions.remove(key);
        if (value == null) {
            return Ternary.UNDEF;
        }
        value = value.trim();
        LOG.debug("Definition: {}={}", key, value);
        if (value.equalsIgnoreCase("true")) {
            return Ternary.TRUE;
        } else if (value.equalsIgnoreCase("false")) {
            return Ternary.FALSE;
        } else {
            throw new IllegalArgumentException(MessageFormat.format(
                    "Invalid option value in definition {0} : {1}",
                    key,
                    value));
        }
    }

    private static void checkRest(Map<String, String> copyDefinitions) {
        assert copyDefinitions != null;
        if (copyDefinitions.isEmpty() == false) {
            throw new IllegalArgumentException(MessageFormat.format(
                    "Unknown definitions: {0}",
                    copyDefinitions.keySet()));
        }
    }

    /**
     * Returns the view of flow IDs which should be skipped.
     * @return the the view of skip targets
     * @since 0.2.6
     */
    Set<String> getSkipFlows() {
        return skipFlows;
    }

    /**
     * Sets whether the flow execution was serialized in {@link #executeBatch(String)}.
     * @param serialize {@code true} to serialize, otherwise {@code false}
     * @since 0.2.6
     */
    void setSerializeFlows(boolean serialize) {
        this.serializeFlows = serialize;
    }

    /**
     * Sets runtime context.
     * Note that the
     * @param runtimeContext the runtime context
     * @throws IllegalArgumentException if some parameters were {@code null}
     * @since 0.4.0
     */
    void setRuntimeContext(RuntimeContext runtimeContext) {
        this.runtimeContext = runtimeContext;
    }

    /**
     * Returns subprocesses' environment variables.
     * @return subprocesses' environment variables
     * @since 0.4.0
     */
    Map<String, String> getEnv() {
        return this.subprocessEnvironmentVaritables;
    }

    /**
     * Executes a target flow.
     * If execution is failed except on {@link ExecutionPhase#SETUP setup}, {@link ExecutionPhase#FINALIZE finalize},
     * or {@link ExecutionPhase#CLEANUP cleanup}, then the {@code finalize} phase will be executed for recovery
     * before the target flow execution is aborted.
     * Execution ID for each flow will automatically generated.
     * @param batchId target batch ID
     * @throws InterruptedException if interrupted during this execution
     * @throws IOException if failed to execute target batch
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public void executeBatch(String batchId) throws InterruptedException, IOException {
        if (batchId == null) {
            throw new IllegalArgumentException("batchId must not be null"); //$NON-NLS-1$
        }
        ExecutorService executor = createJobflowExecutor(batchId);
        YSLOG.info("I01000", batchId);
        long start = System.currentTimeMillis();
        try {
            ExecutionLock lock = acquireExecutionLock(batchId);
            try {
                BatchScheduler batchScheduler = new BatchScheduler(batchId, script, lock, executor);
                batchScheduler.run();
            } finally {
                lock.close();
            }
            YSLOG.info("I01001", batchId);
        } catch (ExitCodeException e) {
            YSLOG.error("E01001", batchId);
            throw e;
        } catch (IOException e) {
            YSLOG.error(e, "E01001", batchId);
            throw e;
        } catch (InterruptedException e) {
            YSLOG.warn(e, "W01001", batchId);
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            YSLOG.info("I01999", batchId, end - start);
        }
    }

    private ExecutionLock acquireExecutionLock(String batchId) throws IOException {
        assert batchId != null;
        if (runtimeContext.canExecute(locks)) {
            return locks.newInstance(batchId);
        } else {
            return ExecutionLock.NULL;
        }
    }

    private PhaseMonitor obtainPhaseMonitor(ExecutionContext context) throws InterruptedException, IOException {
        assert context != null;
        if (runtimeContext.canExecute(monitors)) {
            return monitors.newInstance(context);
        } else {
            return PhaseMonitor.NULL;
        }
    }

    private ExecutorService createJobflowExecutor(final String batchId) {
        assert batchId != null;
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName(MessageFormat.format(
                        "JobflowExecutor-{0}",
                        batchId));
                thread.setDaemon(true);
                return thread;
            }
        };
        if (serializeFlows) {
            return Executors.newFixedThreadPool(1, threadFactory);
        } else {
            return Executors.newCachedThreadPool(threadFactory);
        }
    }

    /**
     * Executes a target flow.
     * If execution is failed except on {@link ExecutionPhase#SETUP setup}, {@link ExecutionPhase#FINALIZE finalize},
     * or {@link ExecutionPhase#CLEANUP cleanup}, then the {@code finalize} phase will be executed for recovery
     * before execution is aborted.
     * @param batchId target batch ID
     * @param flowId target flow ID
     * @param executionId target execution ID
     * @throws InterruptedException if interrupted during this execution
     * @throws IOException if failed to execute target flow
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public void executeFlow(
            String batchId,
            String flowId,
            String executionId) throws InterruptedException, IOException {
        if (batchId == null) {
            throw new IllegalArgumentException("batchId must not be null"); //$NON-NLS-1$
        }
        if (flowId == null) {
            throw new IllegalArgumentException("flowId must not be null"); //$NON-NLS-1$
        }
        if (executionId == null) {
            throw new IllegalArgumentException("executionId must not be null"); //$NON-NLS-1$
        }
        FlowScript flow = script.findFlow(flowId);
        if (flow == null) {
            throw new IllegalArgumentException(MessageFormat.format(
                    "Flow is undefined: batchId={0}, flowId={1}, executionId={2}",
                    batchId,
                    flowId,
                    executionId));
        }
        ExecutionLock lock = acquireExecutionLock(batchId);
        try {
            lock.beginFlow(flowId, executionId);
            executeFlow(batchId, flow, executionId);
            lock.endFlow(flowId, executionId);
        } finally {
            lock.close();
        }
    }

    /**
     * Executes a target phase.
     * @param batchId target batch ID
     * @param flowId target flow ID
     * @param executionId target execution ID
     * @param phase target phase
     * @throws InterruptedException if interrupted during this execution
     * @throws IOException if failed to execute target phase
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public void executePhase(
            String batchId,
            String flowId,
            String executionId,
            ExecutionPhase phase) throws InterruptedException, IOException {
        if (batchId == null) {
            throw new IllegalArgumentException("batchId must not be null"); //$NON-NLS-1$
        }
        if (flowId == null) {
            throw new IllegalArgumentException("flowId must not be null"); //$NON-NLS-1$
        }
        if (executionId == null) {
            throw new IllegalArgumentException("executionId must not be null"); //$NON-NLS-1$
        }
        if (phase == null) {
            throw new IllegalArgumentException("phase must not be null"); //$NON-NLS-1$
        }
        ExecutionContext context = new ExecutionContext(
                batchId, flowId, executionId, phase, batchArguments, subprocessEnvironmentVaritables);
        executePhase(context);
    }

    /**
     * Executes a target phase.
     * @param context the current context
     * @throws InterruptedException if interrupted during this execution
     * @throws IOException if failed to execute target phase
     * @throws IllegalArgumentException if some parameters were {@code null}
     * @since 0.2.5
     */
    public void executePhase(ExecutionContext context) throws InterruptedException, IOException {
        if (context == null) {
            throw new IllegalArgumentException("context must not be null"); //$NON-NLS-1$
        }
        FlowScript flow = script.findFlow(context.getFlowId());
        if (flow == null) {
            throw new IllegalArgumentException(MessageFormat.format(
                    "Flow is undefined: batchId={0}, flowId={1}, executionId={2}",
                    context.getBatchId(),
                    context.getFlowId(),
                    context.getExecutionId()));
        }
        Set<ExecutionScript> executions = flow.getScripts().get(context.getPhase());
        ExecutionLock lock = acquireExecutionLock(context.getBatchId());
        try {
            lock.beginFlow(context.getFlowId(), context.getExecutionId());
            executePhase(context, executions);
            lock.endFlow(context.getFlowId(), context.getExecutionId());
        } finally {
            lock.close();
        }
    }

    void executeFlow(String batchId, FlowScript flow, String executionId) throws InterruptedException, IOException {
        assert batchId != null;
        assert flow != null;
        assert executionId != null;
        YSLOG.info("I02000", batchId, flow.getId(), executionId);
        long start = System.currentTimeMillis();
        try {
            if (skipFlows.contains(flow.getId())) {
                YSLOG.info("I02002", batchId, flow.getId(), executionId);
                return;
            }
            executePhase(batchId, flow, executionId, ExecutionPhase.SETUP);
            boolean succeed = false;
            try {
                executePhase(batchId, flow, executionId, ExecutionPhase.INITIALIZE);
                executePhase(batchId, flow, executionId, ExecutionPhase.IMPORT);
                executePhase(batchId, flow, executionId, ExecutionPhase.PROLOGUE);
                executePhase(batchId, flow, executionId, ExecutionPhase.MAIN);
                executePhase(batchId, flow, executionId, ExecutionPhase.EPILOGUE);
                executePhase(batchId, flow, executionId, ExecutionPhase.EXPORT);
                succeed = true;
            } finally {
                if (succeed) {
                    executePhase(batchId, flow, executionId, ExecutionPhase.FINALIZE);
                } else {
                    YSLOG.info("I02003", batchId, flow.getId(), executionId);
                    try {
                        executePhase(batchId, flow, executionId, ExecutionPhase.FINALIZE);
                    } catch (Exception e) {
                        YSLOG.warn(e, "W02002", batchId, flow.getId(), executionId);
                    }
                }
            }
            try {
                executePhase(batchId, flow, executionId, ExecutionPhase.CLEANUP);
            } catch (Exception e) {
                YSLOG.warn(e, "W02003", batchId, flow.getId(), executionId);
            }
            YSLOG.info("I02001", batchId, flow.getId(), executionId);
        } catch (ExitCodeException e) {
            YSLOG.error("E02001", batchId, flow.getId(), executionId);
            throw e;
        } catch (IOException e) {
            YSLOG.error(e, "E02001", batchId, flow.getId(), executionId);
            throw e;
        } catch (InterruptedException e) {
            YSLOG.warn(e, "W02001", batchId, flow.getId(), executionId);
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            YSLOG.info("I02999", batchId, flow.getId(), executionId, end - start);
        }
    }

    private void executePhase(
            String batchId,
            FlowScript flow,
            String executionId,
            ExecutionPhase phase) throws InterruptedException, IOException {
        ExecutionContext context = new ExecutionContext(
                batchId, flow.getId(), executionId, phase, batchArguments, subprocessEnvironmentVaritables);
        Set<ExecutionScript> scripts = flow.getScripts().get(phase);
        assert scripts != null;
        executePhase(context, scripts);
    }

    private void executePhase(
            ExecutionContext context,
            Set<ExecutionScript> executions) throws InterruptedException, IOException {
        assert context != null;
        assert executions != null;
        YSLOG.info("I03000",
                context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase());
        long start = System.currentTimeMillis();
        try {
            if (skipFlows.contains(context.getFlowId())) {
                YSLOG.info("I03002",
                        context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase());
                return;
            }
            List<? extends Job> jobs;
            ErrorHandler handler;
            switch (context.getPhase()) {
            case SETUP:
                jobs = buildSetupJobs(context);
                handler = JobScheduler.STRICT;
                break;
            case CLEANUP:
                jobs = buildCleanupJobs(context);
                handler = JobScheduler.BEST_EFFORT;
                break;
            case FINALIZE:
                jobs = buildExecutionJobs(context, executions);
                handler = JobScheduler.BEST_EFFORT;
                break;
            default:
                jobs = buildExecutionJobs(context, executions);
                handler = JobScheduler.STRICT;
                break;
            }
            PhaseMonitor monitor = obtainPhaseMonitor(context);
            try {
                scheduler.execute(monitor, context, jobs, handler);
            } finally {
                monitor.close();
            }
            YSLOG.info("I03001",
                    context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase());
        } catch (ExitCodeException e) {
            YSLOG.error("E03001",
                    context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase());
            throw e;
        } catch (IOException e) {
            YSLOG.error(e, "E03001",
                    context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase());
            throw e;
        } catch (InterruptedException e) {
            YSLOG.warn(e, "W03001",
                    context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase());
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            YSLOG.info("I03999",
                    context.getBatchId(), context.getFlowId(), context.getExecutionId(), context.getPhase(),
                    end - start);
        }
    }

    private List<SetupJob> buildSetupJobs(ExecutionContext context) {
        assert context != null;
        List<SetupJob> results = new ArrayList<SetupJob>();
        results.add(new SetupJob(hadoopHandler));
        for (CommandScriptHandler commandHandler : commandHandlers.values()) {
            results.add(new SetupJob(commandHandler));
        }
        return results;
    }

    private List<CleanupJob> buildCleanupJobs(ExecutionContext context) {
        assert context != null;
        List<CleanupJob> results = new ArrayList<CleanupJob>();
        results.add(new CleanupJob(hadoopHandler));
        for (CommandScriptHandler commandHandler : commandHandlers.values()) {
            results.add(new CleanupJob(commandHandler));
        }
        return results;
    }

    private List<ScriptJob<?>> buildExecutionJobs(
            ExecutionContext context,
            Set<ExecutionScript> executions) throws IOException, InterruptedException {
        assert context != null;
        assert executions != null;
        List<ScriptJob<?>> results = new ArrayList<ScriptJob<?>>();
        for (ExecutionScript execution : executions) {
            switch (execution.getKind()) {
            case COMMAND: {
                CommandScript exec = (CommandScript) execution;
                String profileName = exec.getProfileName();
                CommandScriptHandler handler = profileName == null ? null : commandHandlers.get(profileName);
                if (handler == null) {
                    LOG.debug("Profile {} is not defined in comand script handlers, try wildcard: {}",
                            profileName,
                            exec.getId());
                    handler = commandHandlers.get(CommandScriptHandler.PROFILE_WILDCARD);
                }
                if (handler == null) {
                    throw new IOException(MessageFormat.format(
                            "Profile \"{5}\" is not defined (batch={0}, flow={1}, phase={2}, module={3}, id={4})",
                            context.getBatchId(),
                            context.getFlowId(),
                            context.getPhase().getSymbol(),
                            exec.getModuleName(),
                            exec.getId(),
                            profileName));
                }
                results.add(new ScriptJob<CommandScript>(exec.resolve(context, handler), handler));
                break;
            }
            case HADOOP: {
                HadoopScript exec = (HadoopScript) execution;
                results.add(new ScriptJob<HadoopScript>(exec.resolve(context, hadoopHandler), hadoopHandler));
                break;
            }
            default:
                throw new AssertionError(MessageFormat.format(
                        "Unknown execution script: {0}",
                        execution));
            }
        }
        return results;
    }

    private class BatchScheduler {

        final String batchId;

        final LinkedList<FlowScript> flows;

        final ExecutionLock lock;

        final ExecutorService executor;

        final Map<String, FlowScriptTask> running;

        final Set<String> blocking;

        final BlockingQueue<FlowScriptTask> doneQueue;

        BatchScheduler(String batchId, BatchScript batchScript, ExecutionLock lock, ExecutorService executor) {
            assert batchId != null;
            assert batchScript != null;
            assert lock != null;
            assert executor != null;
            this.batchId = batchId;
            this.flows = new LinkedList<FlowScript>(batchScript.getAllFlows());
            this.lock = lock;
            this.executor = executor;
            this.running = new HashMap<String, FlowScriptTask>();
            this.blocking = new HashSet<String>();
            for (FlowScript flow : flows) {
                blocking.add(flow.getId());
            }
            this.doneQueue = new LinkedBlockingQueue<FlowScriptTask>();
        }

        public void run() throws InterruptedException, IOException {
            try {
                while (flows.isEmpty() == false) {
                    boolean submitted = submit();
                    if (submitted == false) {
                        if (running.isEmpty()) {
                            throw new IOException(MessageFormat.format(
                                    "Deadlock was detected in \"{0}\": {1}",
                                    batchId,
                                    blocking));
                        }
                        waitForComplete();
                    }
                }
                while (running.isEmpty() == false) {
                    waitForComplete();
                }
            } finally {
                YSLOG.info("I01004", batchId);
                for (FlowScriptTask task : running.values()) {
                    task.cancel(true);
                }
                while (running.isEmpty() == false) {
                    try {
                        waitForComplete();
                    } catch (IOException e) {
                        YSLOG.warn(e, "W01002", batchId);
                    }
                }
            }
        }

        private boolean submit() {
            LOG.debug("Submitting waiting jobflows: {}", batchId);
            boolean submitted = false;
            for (Iterator<FlowScript> iter = flows.iterator(); iter.hasNext();) {
                FlowScript flow = iter.next();
                boolean blocked = false;
                for (String blockerId : flow.getBlockerIds()) {
                    if (blocking.contains(blockerId)) {
                        blocked = true;
                        break;
                    }
                }
                if (blocked == false) {
                    submit(flow);
                    iter.remove();
                    submitted = true;
                }
            }
            return submitted;
        }

        private void submit(final FlowScript flow) {
            LOG.debug("Submitting jobflow \"{}\": {}", flow.getId(), batchId);
            FlowScriptTask task = new FlowScriptTask(flow, doneQueue, new Callable<Void>() {
                @Override
                public Void call() throws InterruptedException, IOException {
                    if (Thread.interrupted()) {
                        throw new InterruptedException();
                    }
                    String executionId = UUID.randomUUID().toString();
                    LOG.debug("Generated execution ID for \"{}\": {}", flow.getId(), executionId);
                    lock.beginFlow(flow.getId(), executionId);
                    executeFlow(batchId, flow, executionId);
                    lock.endFlow(flow.getId(), executionId);
                    LOG.debug("Completing jobflow \"{}\": {}", flow.getId(), batchId);
                    return null;
                }
            });
            YSLOG.info("I01003", batchId, flow.getId());
            executor.execute(task);
            running.put(flow.getId(), task);
        }

        private void waitForComplete() throws InterruptedException, IOException {
            LOG.debug("Waiting for running jobflows complete: {}", batchId);
            FlowScriptTask done = doneQueue.take();
            assert done.isDone();
            FlowScript flow = done.script;
            try {
                done.get();
                boolean blocked = blocking.remove(flow.getId());
                assert blocked;
            } catch (CancellationException e) {
                YSLOG.info(e, "I01005", batchId, flow.getId());
            } catch (ExecutionException e) {
                if (e.getCause() instanceof IOException) {
                    throw (IOException) e.getCause();
                } else if (e.getCause() instanceof InterruptedException) {
                    throw (InterruptedException) e.getCause();
                } else if (e.getCause() instanceof Error) {
                    throw (Error) e.getCause();
                } else {
                    throw new IOException("Flow execution failed by unknown error", e);
                }
            } finally {
                FlowScriptTask ran = running.remove(flow.getId());
                assert ran != null;
            }
        }
    }

    private static class FlowScriptTask extends FutureTask<Void> {

        final FlowScript script;

        private final BlockingQueue<FlowScriptTask> doneQueue;

        FlowScriptTask(FlowScript script, BlockingQueue<FlowScriptTask> doneQueue, Callable<Void> callable) {
            super(callable);
            assert script != null;
            assert doneQueue != null;
            this.script = script;
            this.doneQueue = doneQueue;
        }

        @Override
        protected void done() {
            try {
                doneQueue.put(this);
            } catch (InterruptedException e) {
                throw new AssertionError(e);
            }
        }
    }

    private enum Ternary {

        TRUE,

        FALSE,

        UNDEF,
    }
}
TOP

Related Classes of com.asakusafw.yaess.core.task.ExecutionTask$FlowScriptTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.