Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2014 LinkedIn Corp.
*
* 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 azkaban.executor;
import static java.util.Objects.requireNonNull;
import azkaban.Constants;
import azkaban.Constants.ConfigurationKeys;
import azkaban.alert.Alerter;
import azkaban.event.EventHandler;
import azkaban.executor.selector.ExecutorComparator;
import azkaban.executor.selector.ExecutorFilter;
import azkaban.executor.selector.ExecutorSelector;
import azkaban.flow.FlowUtils;
import azkaban.metrics.CommonMetrics;
import azkaban.project.Project;
import azkaban.project.ProjectWhitelist;
import azkaban.utils.AuthenticationUtils;
import azkaban.utils.FileIOUtils.JobMetaData;
import azkaban.utils.FileIOUtils.LogData;
import azkaban.utils.JSONUtils;
import azkaban.utils.Pair;
import azkaban.utils.Props;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Thread.State;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
/**
* Executor manager used to manage the client side job.
*/
@Singleton
public class ExecutorManager extends EventHandler implements
ExecutorManagerAdapter {
private static final String SPARK_JOB_TYPE = "spark";
private static final String APPLICATION_ID = "${application.id}";
// The regex to look for while fetching application ID from the Hadoop/Spark job log
private static final Pattern APPLICATION_ID_PATTERN = Pattern
.compile("application_\\d+_\\d+");
// The regex to look for while validating the content from RM job link
private static final Pattern FAILED_TO_READ_APPLICATION_PATTERN = Pattern
.compile("Failed to read the application");
private static final Pattern INVALID_APPLICATION_ID_PATTERN = Pattern
.compile("Invalid Application ID");
private static final int DEFAULT_MAX_ONCURRENT_RUNS_ONEFLOW = 30;
// 12 weeks
private static final long DEFAULT_EXECUTION_LOGS_RETENTION_MS = 3 * 4 * 7
* 24 * 60 * 60 * 1000L;
private static final Duration RECENTLY_FINISHED_LIFETIME = Duration.ofMinutes(10);
private static final Logger logger = Logger.getLogger(ExecutorManager.class);
private final AlerterHolder alerterHolder;
private final Props azkProps;
private final CommonMetrics commonMetrics;
private final ExecutorLoader executorLoader;
private final CleanerThread cleanerThread;
private final ConcurrentHashMap> runningFlows =
new ConcurrentHashMap<>();
private final ExecutingManagerUpdaterThread executingManager;
private final ExecutorApiGateway apiGateway;
private final int maxConcurrentRunsOneFlow;
QueuedExecutions queuedFlows;
File cacheDir;
//make it immutable to ensure threadsafety
private volatile ImmutableSet activeExecutors = null;
private QueueProcessorThread queueProcessor;
private volatile Pair runningCandidate = null;
private long lastCleanerThreadCheckTime = -1;
private long lastThreadCheckTime = -1;
private String updaterStage = "not started";
private List filterList;
private Map comparatorWeightsMap;
private long lastSuccessfulExecutorInfoRefresh;
private ExecutorService executorInforRefresherService;
@Inject
public ExecutorManager(final Props azkProps, final ExecutorLoader loader,
final AlerterHolder alerterHolder,
final CommonMetrics commonMetrics,
final ExecutorApiGateway apiGateway) throws ExecutorManagerException {
this.alerterHolder = alerterHolder;
this.azkProps = azkProps;
this.commonMetrics = commonMetrics;
this.executorLoader = loader;
this.apiGateway = apiGateway;
this.setupExecutors();
this.loadRunningFlows();
this.queuedFlows = new QueuedExecutions(
azkProps.getLong(Constants.ConfigurationKeys.WEBSERVER_QUEUE_SIZE, 100000));
// The default threshold is set to 30 for now, in case some users are affected. We may
// decrease this number in future, to better prevent DDos attacks.
this.maxConcurrentRunsOneFlow = azkProps
.getInt(Constants.ConfigurationKeys.MAX_CONCURRENT_RUNS_ONEFLOW,
DEFAULT_MAX_ONCURRENT_RUNS_ONEFLOW);
this.loadQueuedFlows();
this.cacheDir = new File(azkProps.getString("cache.directory", "cache"));
this.executingManager = new ExecutingManagerUpdaterThread();
if (isMultiExecutorMode()) {
setupMultiExecutorMode();
}
final long executionLogsRetentionMs =
azkProps.getLong("execution.logs.retention.ms",
DEFAULT_EXECUTION_LOGS_RETENTION_MS);
this.cleanerThread = new CleanerThread(executionLogsRetentionMs);
}
public void start() {
this.executingManager.start();
this.cleanerThread.start();
if (isMultiExecutorMode()) {
this.queueProcessor.start();
}
}
private String findApplicationIdFromLog(final String logData) {
final Matcher matcher = APPLICATION_ID_PATTERN.matcher(logData);
String appId = null;
if (matcher.find()) {
appId = matcher.group().substring(12);
}
this.logger.info("Application ID is " + appId);
return appId;
}
private void setupMultiExecutorMode() {
// initliatize hard filters for executor selector from azkaban.properties
final String filters = this.azkProps
.getString(Constants.ConfigurationKeys.EXECUTOR_SELECTOR_FILTERS, "");
if (filters != null) {
this.filterList = Arrays.asList(StringUtils.split(filters, ","));
}
// initliatize comparator feature weights for executor selector from
// azkaban.properties
final Map compListStrings = this.azkProps
.getMapByPrefix(Constants.ConfigurationKeys.EXECUTOR_SELECTOR_COMPARATOR_PREFIX);
if (compListStrings != null) {
this.comparatorWeightsMap = new TreeMap<>();
for (final Map.Entry entry : compListStrings.entrySet()) {
this.comparatorWeightsMap.put(entry.getKey(), Integer.valueOf(entry.getValue()));
}
}
this.executorInforRefresherService =
Executors.newFixedThreadPool(this.azkProps.getInt(
Constants.ConfigurationKeys.EXECUTORINFO_REFRESH_MAX_THREADS, 5));
// configure queue processor
this.queueProcessor =
new QueueProcessorThread(
this.azkProps.getBoolean(Constants.ConfigurationKeys.QUEUEPROCESSING_ENABLED, true),
this.azkProps.getLong(Constants.ConfigurationKeys.ACTIVE_EXECUTOR_REFRESH_IN_MS, 50000),
this.azkProps.getInt(
Constants.ConfigurationKeys.ACTIVE_EXECUTOR_REFRESH_IN_NUM_FLOW, 5),
this.azkProps.getInt(
Constants.ConfigurationKeys.MAX_DISPATCHING_ERRORS_PERMITTED,
this.activeExecutors.size()));
}
/**
* {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#setupExecutors()
*/
@Override
public void setupExecutors() throws ExecutorManagerException {
final Set newExecutors = new HashSet<>();
if (isMultiExecutorMode()) {
logger.info("Initializing multi executors from database.");
newExecutors.addAll(this.executorLoader.fetchActiveExecutors());
} else if (this.azkProps.containsKey(ConfigurationKeys.EXECUTOR_PORT)) {
// add local executor, if specified as per properties
final String executorHost = this.azkProps
.getString(Constants.ConfigurationKeys.EXECUTOR_HOST, "localhost");
final int executorPort = this.azkProps.getInt(ConfigurationKeys.EXECUTOR_PORT);
logger.info(String.format("Initializing local executor %s:%d",
executorHost, executorPort));
Executor executor =
this.executorLoader.fetchExecutor(executorHost, executorPort);
if (executor == null) {
executor = this.executorLoader.addExecutor(executorHost, executorPort);
} else if (!executor.isActive()) {
executor.setActive(true);
this.executorLoader.updateExecutor(executor);
}
newExecutors.add(new Executor(executor.getId(), executorHost,
executorPort, true));
} else {
// throw exception when in single executor mode and no executor port specified in azkaban
// properties
//todo chengren311: convert to slf4j and parameterized logging
final String error = "Missing" + ConfigurationKeys.EXECUTOR_PORT + " in azkaban properties.";
logger.error(error);
throw new ExecutorManagerException(error);
}
if (newExecutors.isEmpty()) {
final String error = "No active executor found";
logger.error(error);
throw new ExecutorManagerException(error);
} else {
this.activeExecutors = ImmutableSet.copyOf(newExecutors);
}
}
private boolean isMultiExecutorMode() {
return this.azkProps.getBoolean(Constants.ConfigurationKeys.USE_MULTIPLE_EXECUTORS, false);
}
/**
* Refresh Executor stats for all the actie executors in this executorManager
*/
private void refreshExecutors() {
final List>> futures =
new ArrayList<>();
for (final Executor executor : this.activeExecutors) {
// execute each executorInfo refresh task to fetch
final Future fetchExecutionInfo =
this.executorInforRefresherService.submit(
() -> this.apiGateway.callForJsonType(executor.getHost(),
executor.getPort(), "/serverStatistics", null, ExecutorInfo.class));
futures.add(new Pair<>(executor,
fetchExecutionInfo));
}
boolean wasSuccess = true;
for (final Pair> refreshPair : futures) {
final Executor executor = refreshPair.getFirst();
executor.setExecutorInfo(null); // invalidate cached ExecutorInfo
try {
// max 5 secs
final ExecutorInfo executorInfo = refreshPair.getSecond().get(5, TimeUnit.SECONDS);
// executorInfo is null if the response was empty
executor.setExecutorInfo(executorInfo);
logger.info(String.format(
"Successfully refreshed executor: %s with executor info : %s",
executor, executorInfo));
} catch (final TimeoutException e) {
wasSuccess = false;
logger.error("Timed out while waiting for ExecutorInfo refresh"
+ executor, e);
} catch (final Exception e) {
wasSuccess = false;
logger.error("Failed to update ExecutorInfo for executor : "
+ executor, e);
}
// update is successful for all executors
if (wasSuccess) {
this.lastSuccessfulExecutorInfoRefresh = System.currentTimeMillis();
}
}
}
/**
* Throws exception if running in local mode {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#disableQueueProcessorThread()
*/
@Override
public void disableQueueProcessorThread() throws ExecutorManagerException {
if (isMultiExecutorMode()) {
this.queueProcessor.setActive(false);
} else {
throw new ExecutorManagerException(
"Cannot disable QueueProcessor in local mode");
}
}
/**
* Throws exception if running in local mode {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#enableQueueProcessorThread()
*/
@Override
public void enableQueueProcessorThread() throws ExecutorManagerException {
if (isMultiExecutorMode()) {
this.queueProcessor.setActive(true);
} else {
throw new ExecutorManagerException(
"Cannot enable QueueProcessor in local mode");
}
}
public State getQueueProcessorThreadState() {
if (isMultiExecutorMode()) {
return this.queueProcessor.getState();
} else {
return State.NEW; // not started in local mode
}
}
/**
* Returns state of QueueProcessor False, no flow is being dispatched True , flows are being
* dispatched as expected
*/
public boolean isQueueProcessorThreadActive() {
if (isMultiExecutorMode()) {
return this.queueProcessor.isActive();
} else {
return false;
}
}
/**
* Return last Successful ExecutorInfo Refresh for all active executors
*/
public long getLastSuccessfulExecutorInfoRefresh() {
return this.lastSuccessfulExecutorInfoRefresh;
}
/**
* Get currently supported Comparators available to use via azkaban.properties
*/
public Set getAvailableExecutorComparatorNames() {
return ExecutorComparator.getAvailableComparatorNames();
}
/**
* Get currently supported filters available to use via azkaban.properties
*/
public Set getAvailableExecutorFilterNames() {
return ExecutorFilter.getAvailableFilterNames();
}
@Override
public State getExecutorManagerThreadState() {
return this.executingManager.getState();
}
public String getExecutorThreadStage() {
return this.updaterStage;
}
@Override
public boolean isExecutorManagerThreadActive() {
return this.executingManager.isAlive();
}
@Override
public long getLastExecutorManagerThreadCheckTime() {
return this.lastThreadCheckTime;
}
@Override
public Collection getAllActiveExecutors() {
return Collections.unmodifiableCollection(this.activeExecutors);
}
/**
* {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#fetchExecutor(int)
*/
@Override
public Executor fetchExecutor(final int executorId) throws ExecutorManagerException {
for (final Executor executor : this.activeExecutors) {
if (executor.getId() == executorId) {
return executor;
}
}
return this.executorLoader.fetchExecutor(executorId);
}
@Override
public Set getPrimaryServerHosts() {
// Only one for now. More probably later.
final HashSet ports = new HashSet<>();
for (final Executor executor : this.activeExecutors) {
ports.add(executor.getHost() + ":" + executor.getPort());
}
return ports;
}
@Override
public Set getAllActiveExecutorServerHosts() {
// Includes non primary server/hosts
final HashSet ports = new HashSet<>();
for (final Executor executor : this.activeExecutors) {
ports.add(executor.getHost() + ":" + executor.getPort());
}
// include executor which were initially active and still has flows running
for (final Pair running : this.runningFlows
.values()) {
final ExecutionReference ref = running.getFirst();
if (ref.getExecutor().isPresent()) {
final Executor executor = ref.getExecutor().get();
ports.add(executor.getHost() + ":" + executor.getPort());
}
}
return ports;
}
private void loadRunningFlows() throws ExecutorManagerException {
logger.info("Loading running flows from database..");
final Map> activeFlows = this.executorLoader
.fetchActiveFlows();
logger.info("Loaded " + activeFlows.size() + " running flows");
this.runningFlows.putAll(activeFlows);
}
/*
* load queued flows i.e with active_execution_reference and not assigned to
* any executor
*/
private void loadQueuedFlows() throws ExecutorManagerException {
final List> retrievedExecutions =
this.executorLoader.fetchQueuedFlows();
if (retrievedExecutions != null) {
for (final Pair pair : retrievedExecutions) {
this.queuedFlows.enqueue(pair.getSecond(), pair.getFirst());
}
}
}
/**
* Gets a list of all the active (running flows and non-dispatched flows) executions for a given
* project and flow {@inheritDoc}. Results should be sorted as we assume this while setting up
* pipelined execution Id.
*
* @see azkaban.executor.ExecutorManagerAdapter#getRunningFlows(int, java.lang.String)
*/
@Override
public List getRunningFlows(final int projectId, final String flowId) {
final List executionIds = new ArrayList<>();
executionIds.addAll(getRunningFlowsHelper(projectId, flowId,
this.queuedFlows.getAllEntries()));
// it's possible an execution is runningCandidate, meaning it's in dispatching state neither in queuedFlows nor runningFlows,
// so checks the runningCandidate as well.
if (this.runningCandidate != null) {
executionIds
.addAll(
getRunningFlowsHelper(projectId, flowId, Lists.newArrayList(this.runningCandidate)));
}
executionIds.addAll(getRunningFlowsHelper(projectId, flowId,
this.runningFlows.values()));
Collections.sort(executionIds);
return executionIds;
}
/* Helper method for getRunningFlows */
private List getRunningFlowsHelper(final int projectId, final String flowId,
final Collection> collection) {
final List executionIds = new ArrayList<>();
for (final Pair ref : collection) {
if (ref.getSecond().getFlowId().equals(flowId)
&& ref.getSecond().getProjectId() == projectId) {
executionIds.add(ref.getFirst().getExecId());
}
}
return executionIds;
}
/**
* {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#getActiveFlowsWithExecutor()
*/
@Override
public List>> getActiveFlowsWithExecutor()
throws IOException {
final List>> flows =
new ArrayList<>();
getActiveFlowsWithExecutorHelper(flows, this.queuedFlows.getAllEntries());
getActiveFlowsWithExecutorHelper(flows, this.runningFlows.values());
return flows;
}
/* Helper method for getActiveFlowsWithExecutor */
private void getActiveFlowsWithExecutorHelper(
final List>> flows,
final Collection> collection) {
for (final Pair ref : collection) {
flows.add(new Pair<>(ref.getSecond(), ref
.getFirst().getExecutor()));
}
}
/**
* Checks whether the given flow has an active (running, non-dispatched) executions {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#isFlowRunning(int, java.lang.String)
*/
@Override
public boolean isFlowRunning(final int projectId, final String flowId) {
boolean isRunning = false;
isRunning =
isRunning
|| isFlowRunningHelper(projectId, flowId, this.queuedFlows.getAllEntries());
isRunning =
isRunning
|| isFlowRunningHelper(projectId, flowId, this.runningFlows.values());
return isRunning;
}
/* Search a running flow in a collection */
private boolean isFlowRunningHelper(final int projectId, final String flowId,
final Collection> collection) {
for (final Pair ref : collection) {
if (ref.getSecond().getProjectId() == projectId
&& ref.getSecond().getFlowId().equals(flowId)) {
return true;
}
}
return false;
}
/**
* Fetch ExecutableFlow from database {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#getExecutableFlow(int)
*/
@Override
public ExecutableFlow getExecutableFlow(final int execId)
throws ExecutorManagerException {
return this.executorLoader.fetchExecutableFlow(execId);
}
/**
* Get all active (running, non-dispatched) flows
*
* {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#getRunningFlows()
*/
@Override
public List getRunningFlows() {
final ArrayList flows = new ArrayList<>();
getActiveFlowHelper(flows, this.queuedFlows.getAllEntries());
getActiveFlowHelper(flows, this.runningFlows.values());
return flows;
}
/*
* Helper method to get all running flows from a Pair flows,
final Collection> collection) {
for (final Pair ref : collection) {
flows.add(ref.getSecond());
}
}
/**
* Get execution Ids of all active (running, non-dispatched) flows
*
* {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#getRunningFlows()
*/
public String getRunningFlowIds() {
final List allIds = new ArrayList<>();
getRunningFlowsIdsHelper(allIds, this.queuedFlows.getAllEntries());
getRunningFlowsIdsHelper(allIds, this.runningFlows.values());
Collections.sort(allIds);
return allIds.toString();
}
/**
* Get execution Ids of all non-dispatched flows
*
* {@inheritDoc}
*
* @see azkaban.executor.ExecutorManagerAdapter#getRunningFlows()
*/
public String getQueuedFlowIds() {
final List allIds = new ArrayList<>();
getRunningFlowsIdsHelper(allIds, this.queuedFlows.getAllEntries());
Collections.sort(allIds);
return allIds.toString();
}
public long getQueuedFlowSize() {
return this.queuedFlows.size();
}
/* Helper method to flow ids of all running flows */
private void getRunningFlowsIdsHelper(final List allIds,
final Collection> collection) {
for (final Pair ref : collection) {
allIds.add(ref.getSecond().getExecutionId());
}
}
@Override
public List getRecentlyFinishedFlows() {
List flows = new ArrayList<>();
try {
flows = this.executorLoader.fetchRecentlyFinishedFlows(
RECENTLY_FINISHED_LIFETIME);
} catch (final ExecutorManagerException e) {
//Todo jamiesjc: fix error handling.
logger.error("Failed to fetch recently finished flows.", e);
}
return flows;
}
@Override
public List getExecutableFlows(final Project project,
final String flowId, final int skip, final int size) throws ExecutorManagerException {
final List flows =
this.executorLoader.fetchFlowHistory(project.getId(), flowId, skip, size);
return flows;
}
@Override
public List getExecutableFlows(final int skip, final int size)
throws ExecutorManagerException {
final List flows = this.executorLoader.fetchFlowHistory(skip, size);
return flows;
}
@Override
public List getExecutableFlows(final String flowIdContains,
final int skip, final int size) throws ExecutorManagerException {
final List flows =
this.executorLoader.fetchFlowHistory(null, '%' + flowIdContains + '%', null,
0, -1, -1, skip, size);
return flows;
}
@Override
public List getExecutableFlows(final String projContain,
final String flowContain, final String userContain, final int status, final long begin,
final long end,
final int skip, final int size) throws ExecutorManagerException {
final List flows =
this.executorLoader.fetchFlowHistory(projContain, flowContain, userContain,
status, begin, end, skip, size);
return flows;
}
@Override
public List getExecutableJobs(final Project project,
final String jobId, final int skip, final int size) throws ExecutorManagerException {
final List nodes =
this.executorLoader.fetchJobHistory(project.getId(), jobId, skip, size);
return nodes;
}
@Override
public int getNumberOfJobExecutions(final Project project, final String jobId)
throws ExecutorManagerException {
return this.executorLoader.fetchNumExecutableNodes(project.getId(), jobId);
}
@Override
public int getNumberOfExecutions(final Project project, final String flowId)
throws ExecutorManagerException {
return this.executorLoader.fetchNumExecutableFlows(project.getId(), flowId);
}
@Override
public LogData getExecutableFlowLog(final ExecutableFlow exFlow, final int offset,
final int length) throws ExecutorManagerException {
final Pair pair =
this.runningFlows.get(exFlow.getExecutionId());
if (pair != null) {
final Pair typeParam = new Pair<>("type", "flow");
final Pair offsetParam =
new Pair<>("offset", String.valueOf(offset));
final Pair lengthParam =
new Pair<>("length", String.valueOf(length));
@SuppressWarnings("unchecked") final Map result =
this.apiGateway.callWithReference(pair.getFirst(), ConnectorParams.LOG_ACTION,
typeParam, offsetParam, lengthParam);
return LogData.createLogDataFromObject(result);
} else {
final LogData value =
this.executorLoader.fetchLogs(exFlow.getExecutionId(), "", 0, offset,
length);
return value;
}
}
@Override
public LogData getExecutionJobLog(final ExecutableFlow exFlow, final String jobId,
final int offset, final int length, final int attempt) throws ExecutorManagerException {
final Pair pair =
this.runningFlows.get(exFlow.getExecutionId());
if (pair != null) {
final Pair typeParam = new Pair<>("type", "job");
final Pair jobIdParam =
new Pair<>("jobId", jobId);
final Pair offsetParam =
new Pair<>("offset", String.valueOf(offset));
final Pair lengthParam =
new Pair<>("length", String.valueOf(length));
final Pair attemptParam =
new Pair<>("attempt", String.valueOf(attempt));
@SuppressWarnings("unchecked") final Map result =
this.apiGateway.callWithReference(pair.getFirst(), ConnectorParams.LOG_ACTION,
typeParam, jobIdParam, offsetParam, lengthParam, attemptParam);
return LogData.createLogDataFromObject(result);
} else {
final LogData value =
this.executorLoader.fetchLogs(exFlow.getExecutionId(), jobId, attempt,
offset, length);
return value;
}
}
@Override
public List