/*
* Copyright (c) 2010-2013 Evolveum
*
* 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.evolveum.midpoint.task.quartzimpl.execution;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.*;
import com.evolveum.midpoint.task.quartzimpl.TaskManagerConfiguration;
import com.evolveum.midpoint.task.quartzimpl.TaskManagerQuartzImpl;
import com.evolveum.midpoint.task.quartzimpl.TaskQuartzImplUtil;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.NodeErrorStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.NodeExecutionStatusType;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
/**
* Manages task threads on the local node. Concerned mainly with stopping threads and querying their state.
*
* @author Pavol Mederly
*/
public class LocalNodeManager {
private static final transient Trace LOGGER = TraceManager.getTrace(LocalNodeManager.class);
private TaskManagerQuartzImpl taskManager;
public LocalNodeManager(TaskManagerQuartzImpl taskManager) {
this.taskManager = taskManager;
}
/*
* =============== SCHEDULER-LEVEL ACTIONS ===============
*
* (used internally by TaskManager)
*/
/**
* Prepares Quartz scheduler. Configures its properties (based on Task Manager configuration) and creates the instance.
* Does not start the scheduler, because this is done during post initialization.
*
* @throws com.evolveum.midpoint.task.api.TaskManagerInitializationException
*/
void initializeScheduler() throws TaskManagerInitializationException {
TaskManagerConfiguration configuration = taskManager.getConfiguration();
Properties quartzProperties = new Properties();
if (configuration.isJdbcJobStore()) {
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
quartzProperties.put("org.quartz.jobStore.driverDelegateClass", configuration.getJdbcDriverDelegateClass());
createQuartzDbSchema(configuration);
String MY_DS = "myDS";
quartzProperties.put("org.quartz.jobStore.dataSource", MY_DS);
if (configuration.getDataSource() != null) {
quartzProperties.put("org.quartz.dataSource."+MY_DS+".jndiURL", configuration.getDataSource());
} else {
quartzProperties.put("org.quartz.dataSource."+MY_DS+".driver", configuration.getJdbcDriver());
quartzProperties.put("org.quartz.dataSource."+MY_DS+".URL", configuration.getJdbcUrl());
quartzProperties.put("org.quartz.dataSource."+MY_DS+".user", configuration.getJdbcUser());
quartzProperties.put("org.quartz.dataSource."+MY_DS+".password", configuration.getJdbcPassword());
}
quartzProperties.put("org.quartz.jobStore.isClustered", configuration.isClustered() ? "true" : "false");
} else {
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
}
quartzProperties.put("org.quartz.scheduler.instanceName", "midPointScheduler");
quartzProperties.put("org.quartz.scheduler.instanceId", taskManager.getNodeId());
quartzProperties.put("org.quartz.scheduler.skipUpdateCheck", "true");
quartzProperties.put("org.quartz.threadPool.threadCount", Integer.toString(configuration.getThreads()));
// in test mode we set idleWaitTime to a lower value, because on some occasions
// the Quartz scheduler "forgots" to fire a trigger immediately after creation,
// and the default delay of 10s is too much for most of the tests.
int schedulerLoopTime;
if (configuration.isTestMode()) {
if (configuration.isJdbcJobStore()) {
schedulerLoopTime = 5000;
} else {
schedulerLoopTime = 2000;
}
} else {
schedulerLoopTime = 10000;
}
quartzProperties.put("org.quartz.scheduler.idleWaitTime", Integer.toString(schedulerLoopTime));
quartzProperties.put("org.quartz.scheduler.jmx.export", "true");
if (configuration.isTestMode()) {
LOGGER.info("ReusableQuartzScheduler is set: the task manager threads will NOT be stopped on shutdown. Also, scheduler threads will run as daemon ones.");
quartzProperties.put("org.quartz.scheduler.makeSchedulerThreadDaemon", "true");
quartzProperties.put("org.quartz.threadPool.makeThreadsDaemons", "true");
}
// initialize the scheduler (without starting it)
try {
LOGGER.trace("Quartz scheduler properties: {}", quartzProperties);
StdSchedulerFactory sf = new StdSchedulerFactory();
sf.initialize(quartzProperties);
getGlobalExecutionManager().setQuartzScheduler(sf.getScheduler());
} catch (SchedulerException e) {
throw new TaskManagerInitializationException("Cannot initialize the Quartz scheduler", e);
}
}
/**
* Creates Quartz database schema, if it does not exist.
*
* @throws TaskManagerInitializationException
* @param configuration
*/
private void createQuartzDbSchema(TaskManagerConfiguration configuration) throws TaskManagerInitializationException {
Connection connection = getConnection(configuration);
LOGGER.debug("createQuartzDbSchema: trying JDBC connection {}", connection);
try {
if (!doQuartzTablesExist(connection)) {
if (configuration.isCreateQuartzTables()) {
try {
Reader scriptReader = getResourceReader(configuration.getSqlSchemaFile());
ScriptRunner runner = new ScriptRunner(connection, false, true);
runner.runScript(scriptReader);
} catch (IOException ex) {
throw new TaskManagerInitializationException("Could not read Quartz database schema file: " + configuration.getSqlSchemaFile(), ex);
} catch (SQLException e) {
throw new TaskManagerInitializationException("Could not create Quartz JDBC Job Store tables from " + configuration.getSqlSchemaFile(), e);
}
if (!doQuartzTablesExist(connection)) {
throw new TaskManagerInitializationException("Quartz tables seem not to exist even after running creation script.");
}
} else {
throw new TaskManagerInitializationException("Quartz tables seem not to exist and their automatic creation is disabled (createQuartzTables is set to false).");
}
}
} finally {
try {
connection.close();
} catch (SQLException ignored) {
}
}
}
private Connection getConnection(TaskManagerConfiguration configuration) throws TaskManagerInitializationException {
Connection connection = null;
try {
if (configuration.getDataSource() != null) {
DataSource dataSource;
try {
InitialContext context = new InitialContext();
dataSource = (DataSource) context.lookup(configuration.getDataSource());
} catch (NamingException e) {
throw new TaskManagerInitializationException("Cannot find a data source '"+configuration.getDataSource()+"': " + e.getMessage(), e);
}
connection = dataSource.getConnection();
} else {
try {
Class.forName(configuration.getJdbcDriver());
} catch (ClassNotFoundException e) {
throw new TaskManagerInitializationException("Could not locate database driver class " + configuration.getJdbcDriver(), e);
}
connection = DriverManager.getConnection(configuration.getJdbcUrl(), configuration.getJdbcUser(), configuration.getJdbcPassword());
}
} catch (SQLException e) {
throw new TaskManagerInitializationException("Cannot create JDBC connection to Quartz Job Store", e);
}
return connection;
}
private boolean doQuartzTablesExist(Connection connection) {
try {
connection.prepareStatement("SELECT count(*) FROM QRTZ_JOB_DETAILS").executeQuery().close();
LOGGER.debug("Quartz tables seem to exist (at least QRTZ_JOB_DETAILS does).");
return true;
} catch (SQLException ignored) {
LOGGER.debug("Quartz tables seem not to exist (at least QRTZ_JOB_DETAILS does not), we got an exception when trying to access it", ignored);
return false;
}
}
private Reader getResourceReader(String name) throws IOException, TaskManagerInitializationException {
InputStream stream = getClass().getResourceAsStream(name);
if (stream == null) {
throw new TaskManagerInitializationException("Quartz DB schema (" + name + ") cannot be found.");
}
return new BufferedReader(new InputStreamReader(stream));
}
private String getResource(String name) throws IOException, TaskManagerInitializationException {
InputStream stream = getClass().getResourceAsStream(name);
if (stream == null) {
throw new TaskManagerInitializationException("Quartz DB schema (" + name + ") cannot be found.");
}
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
StringBuffer sb = new StringBuffer();
int i;
while ((i = br.read()) != -1) {
sb.append((char) i);
}
return sb.toString();
}
void pauseScheduler(OperationResult result) {
LOGGER.info("Putting Quartz scheduler into standby mode");
try {
getQuartzScheduler().standby();
result.recordSuccess();
} catch (SchedulerException e1) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't put local Quartz scheduler into standby mode", e1);
result.recordFatalError("Couldn't put local Quartz scheduler into standby mode", e1);
}
}
void shutdownScheduler() throws TaskManagerException {
LOGGER.info("Shutting down Quartz scheduler");
try {
if (getQuartzScheduler() != null && !getQuartzScheduler().isShutdown()) {
getQuartzScheduler().shutdown(true);
}
LOGGER.info("Quartz scheduler was shut down");
} catch (SchedulerException e) {
throw new TaskManagerException("Cannot shutdown Quartz scheduler", e);
}
}
private Scheduler getQuartzScheduler() {
return getGlobalExecutionManager().getQuartzScheduler();
}
/*
* The following methods are used by external clients, e.g. the admin GUI.
* They do not throw exceptions.
*/
boolean stopSchedulerAndTasks(long timeToWait, OperationResult parentResult) {
OperationResult result = parentResult.createSubresult(LocalNodeManager.class.getName() + ".stopSchedulerAndTasks");
result.addParam("timeToWait", timeToWait);
pauseScheduler(result);
boolean tasksStopped = getGlobalExecutionManager().stopAllTasksOnThisNodeAndWait(timeToWait, result);
LOGGER.info("Scheduler stopped; " + (tasksStopped ? "all task threads have been stopped as well." : "some task threads may still run."));
result.recordSuccessIfUnknown();
return tasksStopped;
}
void stopScheduler(OperationResult result) {
pauseScheduler(result);
}
void startScheduler(OperationResult result) {
if (taskManager.isInErrorState()) {
String message = "Cannot start the scheduler, because Task Manager is in error state (" + taskManager.getLocalNodeErrorStatus() + ")";
LOGGER.error(message);
result.recordFatalError(message);
return;
}
try {
LOGGER.info("Starting the Quartz scheduler");
getQuartzScheduler().start();
LOGGER.debug("Quartz scheduler started.");
result.recordSuccess();
} catch (SchedulerException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Cannot (re)start Quartz scheduler.", e);
result.recordFatalError("Cannot (re)start Quartz scheduler.", e);
}
}
public NodeExecutionStatusType getLocalNodeExecutionStatus() {
if (taskManager.getLocalNodeErrorStatus() != NodeErrorStatusType.OK) {
return NodeExecutionStatusType.ERROR;
} else {
Boolean quartzRunning = isQuartzSchedulerRunning();
if (quartzRunning == null) { // this should not occur if error status is OK
return NodeExecutionStatusType.COMMUNICATION_ERROR;
} else {
return quartzRunning ? NodeExecutionStatusType.RUNNING : NodeExecutionStatusType.PAUSED;
}
}
}
private Boolean isQuartzSchedulerRunning() {
Scheduler quartzScheduler = getGlobalExecutionManager().getQuartzScheduler();
if (quartzScheduler == null) {
return null;
}
try {
return quartzScheduler.isStarted() && !quartzScheduler.isInStandbyMode() && !quartzScheduler.isShutdown();
} catch (SchedulerException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Cannot determine Quartz scheduler state", e);
return null;
}
}
public boolean isRunning() {
Boolean retval = isQuartzSchedulerRunning();
if (retval == null) {
return false; // should not occur anyway
} else {
return retval;
}
}
/*
* ==================== STOP TASK METHODS: "soft" and "hard" ====================
*
* soft = set 'canRun' to false
* hard = call Thread.interrupt
*/
// local task should be running
void stopLocalTaskRun(String oid, OperationResult parentResult) {
OperationResult result = parentResult.createSubresult(LocalNodeManager.class.getName() + ".stopLocalTaskRun");
result.addParam("task", oid);
LOGGER.info("Stopping local task " + oid + " run");
try {
getQuartzScheduler().interrupt(TaskQuartzImplUtil.createJobKeyForTaskOid(oid));
result.recordSuccess();
} catch (UnableToInterruptJobException e) {
String message = "Unable to interrupt the task " + oid;
LoggingUtils.logUnexpectedException(LOGGER, message, e); // however, we continue (e.g. to suspend the task)
result.recordFatalError(message, e);
}
}
/**
* Calls Thread.interrupt() on a local thread hosting task with a given OID.
*/
void interruptLocalTaskThread(String oid) {
LOGGER.trace("Trying to find and interrupt a local execution thread for task {} (if it exists).", oid);
try {
List<JobExecutionContext> jecs = getQuartzScheduler().getCurrentlyExecutingJobs();
for (JobExecutionContext jec : jecs) {
String oid1 = jec.getJobDetail().getKey().getName();
if (oid.equals(oid1)) {
Job job = jec.getJobInstance();
if (job instanceof JobExecutor) {
JobExecutor jobExecutor = (JobExecutor) job;
jobExecutor.sendThreadInterrupt();
}
break;
}
}
} catch (SchedulerException e1) {
LoggingUtils.logUnexpectedException(LOGGER, "Cannot find the currently executing job for the task {}", e1, oid);
// ...and ignore it.
}
}
/*
* ==================== THREAD QUERY METHODS ====================
*/
boolean isTaskThreadActiveLocally(String oid) {
try {
for (JobExecutionContext jec : getQuartzScheduler().getCurrentlyExecutingJobs()) {
if (oid.equals(jec.getJobDetail().getKey().getName())) {
return true;
}
}
} catch (SchedulerException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Cannot get the list of currently executing jobs", e);
return false;
}
return false;
}
/**
* Returns all the currently executing tasks.
*
* @return
*/
Set<Task> getLocallyRunningTasks(OperationResult parentResult) {
OperationResult result = parentResult.createSubresult(LocalNodeManager.class.getName() + ".getLocallyRunningTasks");
Set<Task> retval = new HashSet<Task>();
List<JobExecutionContext> jecs;
try {
jecs = getQuartzScheduler().getCurrentlyExecutingJobs();
} catch (SchedulerException e1) {
String message = "Cannot get the list of currently executing jobs on local node.";
result.recordFatalError(message, e1);
LoggingUtils.logUnexpectedException(LOGGER, message, e1);
return retval;
}
for (JobExecutionContext jec : jecs) {
String oid = jec.getJobDetail().getKey().getName();
OperationResult result1 = result.createSubresult(LocalNodeManager.class.getName() + ".getLocallyRunningTask");
try {
retval.add(taskManager.getTask(oid, result1));
result1.recordSuccess();
} catch (ObjectNotFoundException e) {
String m = "Cannot get the task with OID " + oid + " as it no longer exists";
LoggingUtils.logException(LOGGER, m, e);
result1.recordHandledError(m, e); // it's OK, the task could disappear in the meantime
} catch (SchemaException e) {
String m = "Cannot get the task with OID " + oid + " due to schema problems";
LoggingUtils.logUnexpectedException(LOGGER, m, e);
result1.recordFatalError(m, e);
}
}
result.computeStatus();
return retval;
}
/*
* Various auxiliary methods
*/
private OperationResult createOperationResult(String methodName) {
return new OperationResult(LocalNodeManager.class.getName() + "." + methodName);
}
public ExecutionManager getGlobalExecutionManager() {
return taskManager.getExecutionManager();
}
}