Hello Sahina Bose,
I'd like you to do a code review. Please visit
https://gerrit.ovirt.org/39273
to review the following change.
Change subject: engine: DB persistent quartz scheduler
......................................................................
engine: DB persistent quartz scheduler
Adding support for persisting quartz scheduler jobs
in the database.
Added a PersistentJobWrapper to handle jobs persisted
in the db. The jobdata map has the instance class name
instead of the instance itself to deal with serialization
issues.
Change-Id: I9a34dac95999cb6b3721d201c116fb5f6089bb61
Signed-off-by: Sahina Bose <[email protected]>
---
M backend/manager/modules/scheduler/pom.xml
A
backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImpl.java
M
backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/FixedDelayJobListener.java
M
backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/JobWrapper.java
A
backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/PersistentJobWrapper.java
A
backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilBaseImpl.java
M
backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilQuartzImpl.java
A
backend/manager/modules/scheduler/src/main/resources/ovirt-db-scheduler.properties
A
backend/manager/modules/scheduler/src/test/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImplTest.java
A
backend/manager/modules/scheduler/src/test/resources/ovirt-db-scheduler-test.properties
M
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/BeanType.java
M
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/EngineEJBUtilsStrategy.java
A packaging/dbscripts/upgrade/03_05_1260_insert_quartz_tables.sql
M packaging/services/ovirt-engine/ovirt-engine.conf.in
M packaging/services/ovirt-engine/ovirt-engine.xml.in
15 files changed, 1,014 insertions(+), 357 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/73/39273/1
diff --git a/backend/manager/modules/scheduler/pom.xml
b/backend/manager/modules/scheduler/pom.xml
index 5e96486..7e7e588 100644
--- a/backend/manager/modules/scheduler/pom.xml
+++ b/backend/manager/modules/scheduler/pom.xml
@@ -29,9 +29,21 @@
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
@@ -39,6 +51,9 @@
<additionalClasspathElements>
<additionalClasspathElement>${basedir}/src/test/java</additionalClasspathElement>
</additionalClasspathElements>
+ <excludes>
+ <exclude>**/DBSchedulerUtilQuartzImplTest.java</exclude>
+ </excludes>
</configuration>
</plugin>
<plugin>
@@ -61,6 +76,22 @@
<profiles>
<profile>
+ <id>enable-dao-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration combine.self="override">
+ <excludes/>
+ <systemPropertyVariables>
+
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
<id>findbugs</id>
<activation>
<activeByDefault>true</activeByDefault>
diff --git
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImpl.java
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImpl.java
new file mode 100644
index 0000000..72880d6
--- /dev/null
+++
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImpl.java
@@ -0,0 +1,216 @@
+package org.ovirt.engine.core.utils.timer;
+
+import static org.quartz.JobBuilder.newJob;
+import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.ejb.ConcurrencyManagement;
+import javax.ejb.ConcurrencyManagementType;
+import javax.ejb.DependsOn;
+import javax.ejb.Singleton;
+import javax.ejb.Startup;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+
+import org.apache.commons.lang.ClassUtils;
+import org.ovirt.engine.core.utils.ResourceUtils;
+import org.ovirt.engine.core.utils.ejb.BeanProxyType;
+import org.ovirt.engine.core.utils.ejb.BeanType;
+import org.ovirt.engine.core.utils.ejb.EjbUtils;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerFactory;
+import org.quartz.impl.StdSchedulerFactory;
+
+@Singleton(name = "PersistentScheduler")
+@DependsOn("LockManager")
+@Startup
+@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
+@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
+public class DBSchedulerUtilQuartzImpl extends SchedulerUtilBaseImpl
implements SchedulerUtil {
+
+ @Override
+ @PostConstruct
+ public void create() {
+ setup();
+ }
+
+ /*
+ * retrieving the quartz scheduler from the factory.
+ */
+ public void setup() {
+ final String QUARTZ_DB_PROPERTIES = "ovirt-db-scheduler.properties";
+ Properties props = null;
+ try {
+ props = ResourceUtils.loadProperties(SchedulerUtil.class,
QUARTZ_DB_PROPERTIES);
+ } catch (IOException exception) {
+ throw new IllegalStateException(
+ "Can't load properties from resource \"" +
+ QUARTZ_DB_PROPERTIES + "\".", exception);
+ }
+ setup(props);
+ }
+
+ public void setup(Properties props) {
+ try {
+
+ SchedulerFactory sf = new StdSchedulerFactory(props);
+ sched = sf.getScheduler();
+ if (sched != null) {
+ sched.start();
+ sched.getListenerManager().addJobListener(new
FixedDelayJobListener(this),
+ jobGroupEquals(Scheduler.DEFAULT_GROUP));
+ } else {
+ log.error("there is a problem with the underlying Scheduler:
null returned");
+ }
+
+ } catch (SchedulerException se) {
+ log.error("there is a problem with the underlying Scheduler: {}",
se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ @PreDestroy
+ public void teardown() {
+ super.shutDown();
+ }
+
+ /**
+ * Returns the single instance of this Class.
+ *
+ * @return a SchedulerUtil instance
+ */
+ public static SchedulerUtil getInstance() {
+ return EjbUtils.findBean(BeanType.PERSISTENT_SCHEDULER,
BeanProxyType.LOCAL);
+ }
+
+ /**
+ * To avoid data serialization issues for jobdata that is persisted in the
database inputParams should be of
+ * primitive or String type
+ */
+ @Override
+ public String scheduleAFixedDelayJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long initialDelay,
+ long taskDelay,
+ TimeUnit timeUnit) {
+ if (!validate(instance, inputTypes)) {
+ return null;
+ }
+ return super.scheduleAFixedDelayJob(instance,
+ methodName,
+ inputTypes,
+ inputParams,
+ initialDelay,
+ taskDelay,
+ timeUnit);
+ }
+
+ /**
+ * To avoid data serialization issues for jobdata that is persisted in the
database inpurParams should be of
+ * primitive or String type
+ */
+ @Override
+ public String scheduleAConfigurableDelayJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long initialDelay,
+ String configurableDelayKeyName,
+ TimeUnit timeUnit) {
+ if (!validate(instance, inputTypes)) {
+ return null;
+ }
+ return super.scheduleAConfigurableDelayJob(instance,
+ methodName,
+ inputTypes,
+ inputParams,
+ initialDelay,
+ configurableDelayKeyName,
+ timeUnit);
+ }
+
+ /**
+ * To avoid data serialization issues for jobdata that is persisted in the
database inpurParams should be of
+ * primitive or String type
+ */
+ @Override
+ public String scheduleAOneTimeJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long initialDelay,
+ TimeUnit timeUnit) {
+ if (!validate(instance, inputTypes)) {
+ return null;
+ }
+ return super.scheduleAOneTimeJob(instance, methodName, inputTypes,
inputParams, initialDelay, timeUnit);
+ }
+
+ /**
+ * To avoid data serialization issues for jobdata that is persisted in the
database inpurParams should be of
+ * primitive or String type
+ */
+ @Override
+ public String scheduleACronJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ String cronExpression) {
+ if (!validate(instance, inputTypes)) {
+ return null;
+ }
+ return super.scheduleACronJob(instance, methodName, inputTypes,
inputParams, cronExpression);
+ }
+
+ @Override
+ protected JobDetail createJobWithBasicMapValues(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams) {
+ String jobName = generateUniqueNameForInstance(instance, methodName);
+ JobDetail job = newJob()
+ .withIdentity(jobName, Scheduler.DEFAULT_GROUP)
+ .ofType(PersistentJobWrapper.class)
+ .build();
+ setBasicMapValues(job.getJobDataMap(), instance, methodName,
inputTypes, inputParams);
+ return job;
+ }
+
+ private boolean validate(Object instance, Class<?>[] inputTypes) {
+ boolean validation = true;
+ for (Class<?> cls : inputTypes) {
+ if (!(cls.isPrimitive() || ClassUtils.wrapperToPrimitive(cls) !=
null || cls.isAssignableFrom(String.class))) {
+ validation = false;
+ log.error("Only primitives or String parameter types are
supported for persistent jobs. '{}' is not supported",
+ cls.getSimpleName());
+ }
+ }
+ return validation;
+ }
+
+ /*
+ * The JobData for persistent jobs should contain only primitives We do
NOT store the instance of the class passed,
+ * only the name.
+ */
+ private void setBasicMapValues(JobDataMap data,
+ Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams) {
+ data.put(RUNNABLE_INSTANCE, instance.getClass().getName());
+ data.put(RUN_METHOD_NAME, methodName);
+ data.put(RUN_METHOD_PARAM, inputParams);
+ data.put(RUN_METHOD_PARAM_TYPE, inputTypes);
+ }
+
+}
diff --git
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/FixedDelayJobListener.java
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/FixedDelayJobListener.java
index 94013d8..0a048b4 100644
---
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/FixedDelayJobListener.java
+++
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/FixedDelayJobListener.java
@@ -61,22 +61,22 @@
// This is being called for all our jobs, so first check if this is a
fixed delay
// job and if not just exit:
- if (!data.containsKey(SchedulerUtilQuartzImpl.FIXED_DELAY_VALUE)) {
+ if (!data.containsKey(SchedulerUtilBaseImpl.FIXED_DELAY_VALUE)) {
return;
}
// generate the new trigger time
- String configValueName =
data.getString(SchedulerUtilQuartzImpl.CONFIGURABLE_DELAY_KEY_NAME);
+ String configValueName =
data.getString(SchedulerUtilBaseImpl.CONFIGURABLE_DELAY_KEY_NAME);
long delay;
if (StringUtils.isEmpty(configValueName)) {
- delay =
data.getLongValue(SchedulerUtilQuartzImpl.FIXED_DELAY_VALUE);
+ delay = data.getLongValue(SchedulerUtilBaseImpl.FIXED_DELAY_VALUE);
} else {
ConfigValues configDelay = ConfigValues.valueOf(configValueName);
delay = Config.<Integer> getValue(configDelay).longValue();
}
- TimeUnit delayUnit = (TimeUnit)
data.getWrappedMap().get(SchedulerUtilQuartzImpl.FIXED_DELAY_TIME_UNIT);
+ TimeUnit delayUnit = (TimeUnit)
data.getWrappedMap().get(SchedulerUtilBaseImpl.FIXED_DELAY_TIME_UNIT);
Date runTime = SchedulerUtilQuartzImpl.getFutureDate(delay, delayUnit);
// generate the new trigger
diff --git
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/JobWrapper.java
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/JobWrapper.java
index bd9e632..e6b81dd 100644
---
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/JobWrapper.java
+++
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/JobWrapper.java
@@ -36,9 +36,9 @@
try {
JobDataMap data = context.getJobDetail().getJobDataMap();
Map paramsMap = data.getWrappedMap();
- methodName = (String)
paramsMap.get(SchedulerUtilQuartzImpl.RUN_METHOD_NAME);
- Object instance =
paramsMap.get(SchedulerUtilQuartzImpl.RUNNABLE_INSTANCE);
- Object[] methodParams = (Object[])
paramsMap.get(SchedulerUtilQuartzImpl.RUN_METHOD_PARAM);
+ methodName = (String)
paramsMap.get(SchedulerUtilBaseImpl.RUN_METHOD_NAME);
+ Object instance = getInstanceToRun(paramsMap);
+ Object[] methodParams = (Object[])
paramsMap.get(SchedulerUtilBaseImpl.RUN_METHOD_PARAM);
String methodKey = getMethodKey(instance.getClass().getName(),
methodName);
Method methodToRun = cachedMethods.get(methodKey);
if (methodToRun == null) {
@@ -66,6 +66,10 @@
}
}
+ protected Object getInstanceToRun(Map paramsMap) {
+ return paramsMap.get(SchedulerUtilBaseImpl.RUNNABLE_INSTANCE);
+ }
+
/**
* go over the class methods and find the method with the
* OnTimerMethodAnnotation and the methodId
diff --git
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/PersistentJobWrapper.java
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/PersistentJobWrapper.java
new file mode 100644
index 0000000..99749c8
--- /dev/null
+++
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/PersistentJobWrapper.java
@@ -0,0 +1,45 @@
+package org.ovirt.engine.core.utils.timer;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class PersistentJobWrapper extends JobWrapper {
+
+ private static final Logger log =
LoggerFactory.getLogger(PersistentJobWrapper.class);
+
+ /**
+ * execute a method within an instance. The instance name and the method
name are expected to be in the context
+ * given object.
+ * @param context
+ * the context for this job.
+ */
+ @Override
+ public void execute(JobExecutionContext context) throws
JobExecutionException {
+ super.execute(context);
+ }
+
+ @Override
+ protected Object getInstanceToRun(Map paramsMap) {
+ String instanceName = (String)
paramsMap.get(SchedulerUtilBaseImpl.RUNNABLE_INSTANCE);
+ try {
+ Class<?> clazz = Class.forName(instanceName);
+ Constructor<?> constructor = clazz.getConstructor();
+ Object instance = constructor.newInstance();
+ return instance;
+ } catch (ClassNotFoundException | NoSuchMethodException |
SecurityException | InstantiationException
+ | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException e) {
+ log.error("could not instantiate class '{}' due to error '{}'",
instanceName, e.getMessage());
+ log.debug("Exception", e);
+ return null;
+ }
+ }
+
+}
diff --git
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilBaseImpl.java
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilBaseImpl.java
new file mode 100644
index 0000000..7cb619a
--- /dev/null
+++
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilBaseImpl.java
@@ -0,0 +1,358 @@
+package org.ovirt.engine.core.utils.timer;
+
+import static org.quartz.CronScheduleBuilder.cronSchedule;
+import static org.quartz.JobKey.jobKey;
+import static org.quartz.TriggerBuilder.newTrigger;
+import static org.quartz.TriggerKey.triggerKey;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.ovirt.engine.core.common.config.Config;
+import org.ovirt.engine.core.common.config.ConfigValues;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class SchedulerUtilBaseImpl implements SchedulerUtil {
+
+ public static final String RUNNABLE_INSTANCE = "runnable.instance";
+ public static final String RUN_METHOD_NAME = "method.name";
+ public static final String RUN_METHOD_PARAM_TYPE = "method.paramType";
+ public static final String RUN_METHOD_PARAM = "method.param";
+ public static final String FIXED_DELAY_VALUE = "fixedDelayValue";
+ public static final String FIXED_DELAY_TIME_UNIT = "fixedDelayTimeUnit";
+ public static final String CONFIGURABLE_DELAY_KEY_NAME =
"configDelayKeyName";
+ private static final String TRIGGER_PREFIX = "trigger";
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+ protected Scheduler sched;
+ private final AtomicLong sequenceNumber = new AtomicLong(Long.MIN_VALUE);
+
+ public static Date getFutureDate(long delay, TimeUnit timeUnit) {
+ if (delay > 0) {
+ return new Date(new Date().getTime() +
TimeUnit.MILLISECONDS.convert(delay, timeUnit));
+ } else {
+ return new Date();
+ }
+ }
+
+ /**
+ * schedules a fixed delay job.
+ *
+ * @param instance
+ * - the instance to activate the method on timeout
+ * @param methodName
+ * - the name of the method to activate on the instance
+ * @param inputTypes
+ * - the method input types
+ * @param inputParams
+ * - the method input parameters
+ * @param initialDelay
+ * - the initial delay before the first activation
+ * @param taskDelay
+ * - the delay between jobs
+ * @param timeUnit
+ * - the unit of time used for initialDelay and taskDelay.
+ * @return the scheduled job id
+ */
+ @Override
+ public String scheduleAFixedDelayJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long initialDelay,
+ long taskDelay,
+ TimeUnit timeUnit) {
+ JobDetail job = createJobForDelayJob(instance, methodName, inputTypes,
inputParams, taskDelay, timeUnit);
+ scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
+ return job.getKey().getName();
+ }
+
+ private void scheduleJobWithTrigger(long initialDelay, TimeUnit timeUnit,
Object instance, JobDetail job) {
+ Trigger trigger = createSimpleTrigger(initialDelay, timeUnit,
instance);
+ try {
+ sched.scheduleJob(job, trigger);
+ } catch (SchedulerException se) {
+ log.error("failed to schedule job: {}", se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ private JobDetail createJobForDelayJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long taskDelay,
+ TimeUnit timeUnit) {
+ JobDetail job = createJobWithBasicMapValues(instance, methodName,
inputTypes, inputParams);
+ JobDataMap data = job.getJobDataMap();
+ setupDataMapForDelayJob(data, taskDelay, timeUnit);
+ return job;
+ }
+
+ /**
+ * schedules a job with a configurable delay.
+ *
+ * @param instance
+ * - the instance to activate the method on timeout
+ * @param methodName
+ * - the name of the method to activate on the instance
+ * @param inputTypes
+ * - the method input types
+ * @param inputParams
+ * - the method input parameters
+ * @param initialDelay
+ * - the initial delay before the first activation
+ * @param taskDelay
+ * - the name of the config value that sets the delay between
jobs
+ * @param timeUnit
+ * - the unit of time used for initialDelay and taskDelay.
+ * @return the scheduled job id
+ */
+ @Override
+ public String scheduleAConfigurableDelayJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long initialDelay,
+ String configurableDelayKeyName,
+ TimeUnit timeUnit) {
+ long configurableDelay =
getConfigurableDelay(configurableDelayKeyName);
+ JobDetail job =
+ createJobForDelayJob(instance, methodName, inputTypes,
inputParams, configurableDelay, timeUnit);
+ JobDataMap data = job.getJobDataMap();
+ data.put(CONFIGURABLE_DELAY_KEY_NAME, configurableDelayKeyName);
+ scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
+ return job.getKey().getName();
+ }
+
+ /**
+ * get the configurable delay value from the DB according to given key
+ *
+ * @param configurableDelayKeyName
+ * @return
+ */
+ private long getConfigurableDelay(String configurableDelayKeyName) {
+ ConfigValues configDelay =
ConfigValues.valueOf(configurableDelayKeyName);
+ return Config.<Integer> getValue(configDelay).longValue();
+ }
+
+ /**
+ * setup the values in the data map that are relevant for jobs with delay
+ */
+ private void setupDataMapForDelayJob(JobDataMap data, long taskDelay,
TimeUnit timeUnit) {
+ data.put(FIXED_DELAY_TIME_UNIT, timeUnit);
+ data.put(FIXED_DELAY_VALUE, taskDelay);
+ }
+
+ protected abstract JobDetail createJobWithBasicMapValues(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams);
+
+ private Trigger createSimpleTrigger(long initialDelay, TimeUnit timeUnit,
Object instance) {
+ Date runTime = getFutureDate(initialDelay, timeUnit);
+ String triggerName = generateUniqueNameForInstance(instance,
TRIGGER_PREFIX);
+ Trigger trigger = newTrigger()
+ .withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
+ .startAt(runTime)
+ .build();
+ return trigger;
+ }
+
+ /**
+ * schedules a one time job.
+ *
+ * @param instance
+ * - the instance to activate the method on timeout
+ * @param methodName
+ * - the name of the method to activate on the instance
+ * @param inputTypes
+ * - the method input types
+ * @param inputParams
+ * - the method input parameters
+ * @param initialDelay
+ * - the initial delay before the job activation
+ * @param timeUnit
+ * - the unit of time used for initialDelay and taskDelay.
+ * @return the scheduled job id
+ */
+ @Override
+ public String scheduleAOneTimeJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ long initialDelay,
+ TimeUnit timeUnit) {
+ JobDetail job = createJobWithBasicMapValues(instance, methodName,
inputTypes, inputParams);
+ scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
+ return job.getKey().getName();
+ }
+
+ /**
+ * schedules a cron job.
+ *
+ * @param instance
+ * - the instance to activate the method on timeout
+ * @param methodName
+ * - the name of the method to activate on the instance
+ * @param inputTypes
+ * - the method input types
+ * @param inputParams
+ * - the method input parameters
+ * @param cronExpression
+ * - cron expression to run this job
+ * @return the scheduled job id
+ */
+ @Override
+ public String scheduleACronJob(Object instance,
+ String methodName,
+ Class<?>[] inputTypes,
+ Object[] inputParams,
+ String cronExpression) {
+ JobDetail job = createJobWithBasicMapValues(instance, methodName,
inputTypes, inputParams);
+ try {
+ String triggerName = generateUniqueNameForInstance(instance,
TRIGGER_PREFIX);
+ Trigger trigger = newTrigger()
+ .withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
+ .withSchedule(cronSchedule(cronExpression))
+ .build();
+ sched.scheduleJob(job, trigger);
+ } catch (Exception se) {
+ log.error("failed to schedule job: {}", se.getMessage());
+ log.debug("Exception", se);
+ }
+ return job.getKey().getName();
+ }
+
+ /**
+ * reschedule the job associated with the given old trigger with the new
trigger.
+ *
+ * @param oldTriggerName
+ * - the name of the trigger to remove.
+ * @param oldTriggerGroup
+ * - the group of the trigger to remove.
+ * @param newTrigger
+ * - the new Trigger to associate the job with
+ */
+ @Override
+ public void rescheduleAJob(String oldTriggerName, String oldTriggerGroup,
Trigger newTrigger) {
+ try {
+ if (!sched.isShutdown()) {
+ sched.rescheduleJob(triggerKey(oldTriggerName,
oldTriggerGroup), newTrigger);
+ }
+ } catch (SchedulerException se) {
+ log.error("failed to reschedule the job: {}", se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ /**
+ * pauses a job with the given jobId assuming the job is in the default
quartz group
+ *
+ * @param jobId
+ */
+ @Override
+ public void pauseJob(String jobId) {
+ try {
+ sched.pauseJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
+ } catch (SchedulerException se) {
+ log.error("failed to pause a job with id={}: {}", jobId,
se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ /**
+ * Delete the identified Job from the Scheduler
+ *
+ * @param jobId
+ * - the id of the job to delete
+ */
+ @Override
+ public void deleteJob(String jobId) {
+ try {
+ sched.deleteJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
+ } catch (SchedulerException se) {
+ log.error("failed to delete a job with id={}: {}", jobId,
se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ /**
+ * resume a job with the given jobId assuming the job is in the default
quartz group
+ *
+ * @param jobId
+ */
+ @Override
+ public void resumeJob(String jobId) {
+ try {
+ sched.resumeJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
+ } catch (SchedulerException se) {
+ log.error("failed to pause a job with id={}: {}", jobId,
se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ @Override
+ public void triggerJob(String jobId) {
+ try {
+ List<? extends Trigger> existingTriggers =
sched.getTriggersOfJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
+
+ if (!existingTriggers.isEmpty()) {
+ // Note: we assume that every job has exactly one trigger
+ Trigger oldTrigger = existingTriggers.get(0);
+ TriggerKey oldTriggerKey = oldTrigger.getKey();
+ Trigger newTrigger = newTrigger()
+ .withIdentity(oldTriggerKey)
+ .startAt(getFutureDate(0, TimeUnit.MILLISECONDS))
+ .build();
+
+ rescheduleAJob(oldTriggerKey.getName(),
oldTriggerKey.getGroup(), newTrigger);
+ } else {
+ log.error("failed to trigger a job with id={}, job has no
trigger", jobId);
+ }
+ } catch (SchedulerException se) {
+ log.error("failed to trigger a job with id={}: {}", jobId,
se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ /**
+ * Halts the Scheduler, and cleans up all resources associated with the
Scheduler. The scheduler cannot be
+ * re-started.
+ *
+ * @see org.quartz.Scheduler#shutdown(boolean waitForJobsToComplete)
+ */
+ @Override
+ public void shutDown() {
+ try {
+ if (sched != null) {
+ sched.shutdown(true);
+ }
+ } catch (SchedulerException se) {
+ log.error("failed to shut down the scheduler: {}",
se.getMessage());
+ log.debug("Exception", se);
+ }
+ }
+
+ /**
+ * @return the quartz scheduler wrapped by this SchedulerUtil
+ */
+ @Override
+ public Scheduler getRawScheduler() {
+ return sched;
+ }
+
+ protected String generateUniqueNameForInstance(Object instance, String
nestedName) {
+ String name = instance.getClass().getName() + "." + nestedName + "#" +
sequenceNumber.incrementAndGet();
+ return name;
+ }
+
+}
diff --git
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilQuartzImpl.java
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilQuartzImpl.java
index c77bae8..8b15599 100644
---
a/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilQuartzImpl.java
+++
b/backend/manager/modules/scheduler/src/main/java/org/ovirt/engine/core/utils/timer/SchedulerUtilQuartzImpl.java
@@ -1,16 +1,7 @@
package org.ovirt.engine.core.utils.timer;
-import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
-import static org.quartz.JobKey.jobKey;
-import static org.quartz.TriggerBuilder.newTrigger;
-import static org.quartz.TriggerKey.triggerKey;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;
-
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@@ -22,10 +13,6 @@
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.ovirt.engine.core.common.config.Config;
-import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.utils.ejb.BeanProxyType;
import org.ovirt.engine.core.utils.ejb.BeanType;
import org.ovirt.engine.core.utils.ejb.EjbUtils;
@@ -34,8 +21,6 @@
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
-import org.quartz.Trigger;
-import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
// Here we use a Singleton bean, names Scheduler.
@@ -49,28 +34,12 @@
@Startup
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
-public class SchedulerUtilQuartzImpl implements SchedulerUtil {
-
- // consts
- public static final String RUNNABLE_INSTANCE = "runnable.instance";
- public static final String RUN_METHOD_NAME = "method.name";
- public static final String RUN_METHOD_PARAM_TYPE = "method.paramType";
- public static final String RUN_METHOD_PARAM = "method.param";
- public static final String FIXED_DELAY_VALUE = "fixedDelayValue";
- public static final String FIXED_DELAY_TIME_UNIT = "fixedDelayTimeUnit";
- public static final String CONFIGURABLE_DELAY_KEY_NAME =
"configDelayKeyName";
- private static final String TRIGGER_PREFIX = "trigger";
-
- // members
- private final Log log = LogFactory.getLog(SchedulerUtilQuartzImpl.class);
- private Scheduler sched;
-
- private final AtomicLong sequenceNumber = new AtomicLong(Long.MIN_VALUE);
-
+public class SchedulerUtilQuartzImpl extends SchedulerUtilBaseImpl {
/**
* This method is called upon the bean creation as part
* of the management Service bean lifecycle.
*/
+ @Override
@PostConstruct
public void create(){
setup();
@@ -110,198 +79,18 @@
return EjbUtils.findBean(BeanType.SCHEDULER, BeanProxyType.LOCAL);
}
- /**
- * schedules a fixed delay job.
- *
- * @param instance
- * - the instance to activate the method on timeout
- * @param methodName
- * - the name of the method to activate on the instance
- * @param inputTypes
- * - the method input types
- * @param inputParams
- * - the method input parameters
- * @param initialDelay
- * - the initial delay before the first activation
- * @param taskDelay
- * - the delay between jobs
- * @param timeUnit
- * - the unit of time used for initialDelay and taskDelay.
- * @return the scheduled job id
- */
@Override
- public String scheduleAFixedDelayJob(Object instance,
- String methodName,
- Class<?>[] inputTypes,
- Object[] inputParams,
- long initialDelay,
- long taskDelay,
- TimeUnit timeUnit) {
- JobDetail job = createJobForDelayJob(instance, methodName, inputTypes,
inputParams, taskDelay, timeUnit);
- scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
- return job.getKey().getName();
- }
-
- private void scheduleJobWithTrigger(long initialDelay, TimeUnit timeUnit,
Object instance, JobDetail job) {
- Trigger trigger = createSimpleTrigger(initialDelay, timeUnit,
instance);
- try {
- sched.scheduleJob(job, trigger);
- } catch (SchedulerException se) {
- log.error("failed to schedule job", se);
- }
- }
-
- private JobDetail createJobForDelayJob(Object instance,
- String methodName,
- Class<?>[] inputTypes,
- Object[] inputParams, long taskDelay, TimeUnit timeUnit) {
- JobDetail job = createJobWithBasicMapValues(instance, methodName,
inputTypes, inputParams);
- JobDataMap data = job.getJobDataMap();
- setupDataMapForDelayJob(data, taskDelay, timeUnit);
- return job;
- }
-
- /**
- * schedules a job with a configurable delay.
- *
- * @param instance
- * - the instance to activate the method on timeout
- * @param methodName
- * - the name of the method to activate on the instance
- * @param inputTypes
- * - the method input types
- * @param inputParams
- * - the method input parameters
- * @param initialDelay
- * - the initial delay before the first activation
- * @param taskDelay
- * - the name of the config value that sets the delay between
jobs
- * @param timeUnit
- * - the unit of time used for initialDelay and taskDelay.
- * @return the scheduled job id
- */
- @Override
- public String scheduleAConfigurableDelayJob(Object instance,
- String methodName,
- Class<?>[] inputTypes,
- Object[] inputParams,
- long initialDelay,
- String configurableDelayKeyName,
- TimeUnit timeUnit) {
- long configurableDelay =
getConfigurableDelay(configurableDelayKeyName);
- JobDetail job = createJobForDelayJob(instance, methodName, inputTypes,
inputParams, configurableDelay, timeUnit);
- JobDataMap data = job.getJobDataMap();
- data.put(CONFIGURABLE_DELAY_KEY_NAME, configurableDelayKeyName);
- scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
- return job.getKey().getName();
- }
-
- /**
- * get the configurable delay value from the DB according to given key
- * @param configurableDelayKeyName
- * @return
- */
- private long getConfigurableDelay(String configurableDelayKeyName) {
- ConfigValues configDelay =
ConfigValues.valueOf(configurableDelayKeyName);
- return Config.<Integer> getValue(configDelay).longValue();
- }
-
- /**
- * setup the values in the data map that are relevant for jobs with delay
- */
- private void setupDataMapForDelayJob(JobDataMap data,
- long taskDelay,
- TimeUnit timeUnit) {
- data.put(FIXED_DELAY_TIME_UNIT, timeUnit);
- data.put(FIXED_DELAY_VALUE, taskDelay);
- }
-
- private JobDetail createJobWithBasicMapValues(Object instance,
+ protected JobDetail createJobWithBasicMapValues(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams) {
String jobName = generateUniqueNameForInstance(instance, methodName);
JobDetail job = newJob()
- .withIdentity(jobName, Scheduler.DEFAULT_GROUP)
- .ofType(JobWrapper.class)
- .build();
+ .withIdentity(jobName, Scheduler.DEFAULT_GROUP)
+ .ofType(JobWrapper.class)
+ .build();
setBasicMapValues(job.getJobDataMap(), instance, methodName,
inputTypes, inputParams);
return job;
- }
-
- private Trigger createSimpleTrigger(long initialDelay, TimeUnit timeUnit,
Object instance) {
- Date runTime = getFutureDate(initialDelay, timeUnit);
- String triggerName = generateUniqueNameForInstance(instance,
TRIGGER_PREFIX);
- Trigger trigger = newTrigger()
- .withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
- .startAt(runTime)
- .build();
- return trigger;
- }
-
- /**
- * schedules a one time job.
- *
- * @param instance
- * - the instance to activate the method on timeout
- * @param methodName
- * - the name of the method to activate on the instance
- * @param inputTypes
- * - the method input types
- * @param inputParams
- * - the method input parameters
- * @param initialDelay
- * - the initial delay before the job activation
- * @param timeUnit
- * - the unit of time used for initialDelay and taskDelay.
- * @return the scheduled job id
- */
- @Override
- public String scheduleAOneTimeJob(Object instance,
- String methodName,
- Class<?>[] inputTypes,
- Object[] inputParams,
- long initialDelay,
- TimeUnit timeUnit) {
- JobDetail job = createJobWithBasicMapValues(instance, methodName,
inputTypes, inputParams);
- scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
- return job.getKey().getName();
- }
-
- /**
- * schedules a cron job.
- *
- * @param instance
- * - the instance to activate the method on timeout
- * @param methodName
- * - the name of the method to activate on the instance
- * @param inputTypes
- * - the method input types
- * @param inputParams
- * - the method input parameters
- * @param cronExpression
- * - cron expression to run this job
- * @return the scheduled job id
- */
- @Override
- public String scheduleACronJob(Object instance,
- String methodName,
- Class<?>[] inputTypes,
- Object[] inputParams,
- String cronExpression) {
- JobDetail job = createJobWithBasicMapValues(instance, methodName,
inputTypes, inputParams);
- try {
- String triggerName = generateUniqueNameForInstance(instance,
TRIGGER_PREFIX);
- Trigger trigger = newTrigger()
- .withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
- .withSchedule(cronSchedule(cronExpression))
- .build();
- sched.scheduleJob(job, trigger);
- } catch (Exception se) {
- log.error("failed to schedule job", se);
- }
-
- return job.getKey().getName();
}
private void setBasicMapValues(JobDataMap data,
@@ -314,137 +103,4 @@
data.put(RUN_METHOD_PARAM, inputParams);
data.put(RUN_METHOD_PARAM_TYPE, inputTypes);
}
-
- /**
- * reschedule the job associated with the given old trigger with the new
- * trigger.
- *
- * @param oldTriggerName
- * - the name of the trigger to remove.
- * @param oldTriggerGroup
- * - the group of the trigger to remove.
- * @param newTrigger
- * - the new Trigger to associate the job with
- */
- public void rescheduleAJob(String oldTriggerName, String oldTriggerGroup,
Trigger newTrigger) {
- try {
- sched.rescheduleJob(triggerKey(oldTriggerName, oldTriggerGroup),
newTrigger);
- } catch (SchedulerException se) {
- log.error("failed to reschedule the job", se);
- }
- }
-
- /**
- * pauses a job with the given jobId assuming the job is in the default
- * quartz group
- *
- * @param jobId
- */
- @Override
- public void pauseJob(String jobId) {
- try {
- sched.pauseJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
- } catch (SchedulerException se) {
- log.error("failed to pause a job with id=" + jobId, se);
- }
-
- }
-
- /**
- * Delete the identified Job from the Scheduler
- *
- * @param jobId
- * - the id of the job to delete
- */
- @Override
- public void deleteJob(String jobId) {
- try {
- sched.deleteJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
- } catch (SchedulerException se) {
- log.error("failed to delete a job with id=" + jobId, se);
- }
-
- }
-
- /**
- * resume a job with the given jobId assuming the job is in the default
- * quartz group
- *
- * @param jobId
- */
- @Override
- public void resumeJob(String jobId) {
- try {
- sched.resumeJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
- } catch (SchedulerException se) {
- log.error("failed to pause a job with id=" + jobId, se);
- }
-
- }
-
- @Override
- public void triggerJob(String jobId) {
- try {
- List<? extends Trigger> existingTriggers =
sched.getTriggersOfJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
-
- if (!existingTriggers.isEmpty()) {
- // Note: we assume that every job has exactly one trigger
- Trigger oldTrigger = existingTriggers.get(0);
- TriggerKey oldTriggerKey = oldTrigger.getKey();
- Trigger newTrigger = newTrigger()
- .withIdentity(oldTriggerKey)
- .startAt(getFutureDate(0, TimeUnit.MILLISECONDS))
- .build();
-
- rescheduleAJob(oldTriggerKey.getName(),
oldTriggerKey.getGroup(), newTrigger);
- } else {
- log.error("failed to trigger a job with id=" + jobId + ", job
has no trigger");
- }
- } catch (SchedulerException se) {
- log.error("failed to trigger a job with id=" + jobId, se);
- }
- }
-
- /**
- * Halts the Scheduler, and cleans up all resources associated with the
- * Scheduler. The scheduler cannot be re-started.
- *
- * @see org.quartz.Scheduler#shutdown(boolean waitForJobsToComplete)
- */
- @Override
- public void shutDown() {
- try {
- sched.shutdown(true);
- } catch (SchedulerException se) {
- log.error("failed to shut down the scheduler", se);
- }
- }
-
- /**
- * @return the quartz scheduler wrapped by this SchedulerUtil
- */
- public Scheduler getRawScheduler() {
- return sched;
- }
-
- /*
- * returns a future date with the given delay. the delay is being
calculated
- * according to the given Time units
- */
- public static Date getFutureDate(long delay, TimeUnit timeUnit) {
- if (delay > 0) {
- return new Date(new Date().getTime() +
TimeUnit.MILLISECONDS.convert(delay, timeUnit));
- } else {
- return new Date();
- }
- }
-
- /*
- * generate a unique name for the given instance, using a sequence number.
- */
- private String generateUniqueNameForInstance(Object instance, String
nestedName) {
- String name = instance.getClass().getName() + "." + nestedName + "#" +
sequenceNumber.incrementAndGet();
- return name;
- }
-
}
diff --git
a/backend/manager/modules/scheduler/src/main/resources/ovirt-db-scheduler.properties
b/backend/manager/modules/scheduler/src/main/resources/ovirt-db-scheduler.properties
new file mode 100644
index 0000000..2193074
--- /dev/null
+++
b/backend/manager/modules/scheduler/src/main/resources/ovirt-db-scheduler.properties
@@ -0,0 +1,13 @@
+org.quartz.scheduler.instanceName=QuartzOvirtDBScheduler
+org.quartz.scheduler.skipUpdateCheck=true
+org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer=true
+org.quartz.jobStore.useProperties=false
+org.quartz.jobStore.lockOnInsert=false
+org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreCMT
+org.quartz.jobStore.driverDelegateClass =
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
+org.quartz.jobStore.dataSource=EngineDS
+org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
+org.quartz.threadPool.threadCount=50
+org.quartz.jobStore.nonManagedTXDataSource=NMEngineDS
+org.quartz.dataSource.EngineDS.jndiURL=java:/ENGINEDataSource
+org.quartz.dataSource.NMEngineDS.jndiURL=java:/ENGINEDataSourceNoJTA
\ No newline at end of file
diff --git
a/backend/manager/modules/scheduler/src/test/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImplTest.java
b/backend/manager/modules/scheduler/src/test/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImplTest.java
new file mode 100644
index 0000000..dfda3b8
--- /dev/null
+++
b/backend/manager/modules/scheduler/src/test/java/org/ovirt/engine/core/utils/timer/DBSchedulerUtilQuartzImplTest.java
@@ -0,0 +1,104 @@
+package org.ovirt.engine.core.utils.timer;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.ovirt.engine.core.utils.ResourceUtils;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+
+public class DBSchedulerUtilQuartzImplTest {
+
+ private static DBSchedulerUtilQuartzImpl scheduler;
+
+ @BeforeClass
+ public static void init() {
+ String QUARTZ_DB_TEST_PROPERTIES =
"ovirt-db-scheduler-test.properties";
+ Properties props = null;
+ try {
+ props = ResourceUtils.loadProperties(SchedulerUtil.class,
QUARTZ_DB_TEST_PROPERTIES);
+ } catch (IOException exception) {
+ throw new IllegalStateException(
+ "Can't load properties from resource \"" +
+ QUARTZ_DB_TEST_PROPERTIES + "\".", exception);
+ }
+ scheduler = new DBSchedulerUtilQuartzImpl();
+ scheduler.setup(props);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ scheduler.teardown();
+ }
+
+ @Test
+ public void scheduleAJob() throws InterruptedException {
+ DummyJob dummyJob = new DummyJob();
+ String jobName = scheduler.scheduleAOneTimeJob(dummyJob,
"dummyScheduleMethod",
+ new Class[] { String.class },
+ new Object[] { "scheduleAJob" }, 1, TimeUnit.MILLISECONDS);
+
+ Thread.sleep(10);
+ try {
+ JobDetail job =
scheduler.getRawScheduler().getJobDetail(JobKey.jobKey(jobName));
+ assertNotNull(job);
+ } catch (SchedulerException e) {
+ fail("Unexpected exception occured -" + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ scheduler.deleteJob(jobName);
+ }
+ }
+
+ @Test
+ public void scheduleARecurringJob() throws InterruptedException {
+ DummyJob dummyJob = new DummyJob();
+ String jobName = scheduler.scheduleACronJob(dummyJob,
"dummyScheduleMethod",
+ new Class[] { String.class },
+ new Object[] { "scheduleARecurringJob" }, "0/1 * * * * ?");
+
+ Thread.sleep(100);
+ try {
+ JobDetail job =
scheduler.getRawScheduler().getJobDetail(JobKey.jobKey(jobName));
+ assertNotNull(job);
+ List<? extends Trigger> triggers =
scheduler.getRawScheduler().getTriggersOfJob(JobKey.jobKey(jobName));
+ assertNotNull(triggers.get(0).getPreviousFireTime());
+ } catch (SchedulerException e) {
+ fail("Unexpected exception occured -" + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ scheduler.deleteJob(jobName);
+ }
+ }
+
+
+}
+
+class DummyJob implements Serializable {
+ private static final long serialVersionUID = 2288097737673782124L;
+ private static String msg;
+
+ public DummyJob() {
+
+ }
+
+ public String getMessage() {
+ return msg;
+ }
+
+ @OnTimerMethodAnnotation("dummyScheduleMethod")
+ public void dummyScheduleMethod(String str) {
+ msg = str;
+ }
+}
diff --git
a/backend/manager/modules/scheduler/src/test/resources/ovirt-db-scheduler-test.properties
b/backend/manager/modules/scheduler/src/test/resources/ovirt-db-scheduler-test.properties
new file mode 100644
index 0000000..0dbbdde
--- /dev/null
+++
b/backend/manager/modules/scheduler/src/test/resources/ovirt-db-scheduler-test.properties
@@ -0,0 +1,23 @@
+org.quartz.scheduler.instanceName=QuartzOvirtDBScheduler
+org.quartz.scheduler.skipUpdateCheck=true
+org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer=true
+org.quartz.jobStore.useProperties=false
+org.quartz.jobStore.lockOnInsert=false
+org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreCMT
+org.quartz.jobStore.driverDelegateClass =
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
+org.quartz.jobStore.dataSource=EngineDS
+org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
+org.quartz.threadPool.threadCount=50
+org.quartz.jobStore.nonManagedTXDataSource=NMEngineDS
+
+org.quartz.dataSource.EngineDS.driver=org.postgresql.Driver
+org.quartz.dataSource.EngineDS.URL=jdbc:postgresql://localhost/engine_dao_tests
+org.quartz.dataSource.EngineDS.user=engine
+org.quartz.dataSource.EngineDS.password=engine
+org.quartz.dataSource.EngineDS.maxConnections=4
+
+org.quartz.dataSource.NMEngineDS.driver=org.postgresql.Driver
+org.quartz.dataSource.NMEngineDS.URL=jdbc:postgresql://localhost/engine_dao_tests
+org.quartz.dataSource.NMEngineDS.user=engine
+org.quartz.dataSource.NMEngineDS.password=engine
+org.quartz.dataSource.NMEngineDS.maxConnections=4
\ No newline at end of file
diff --git
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/BeanType.java
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/BeanType.java
index 849de08..42119fb 100644
---
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/BeanType.java
+++
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/BeanType.java
@@ -8,6 +8,7 @@
public enum BeanType {
BACKEND, // Backend bean
SCHEDULER, // SchedulerUtil
+ PERSISTENT_SCHEDULER,
VDS_EVENT_LISTENER,
LOCK_MANAGER,
EVENTQUEUE_MANAGER,
diff --git
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/EngineEJBUtilsStrategy.java
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/EngineEJBUtilsStrategy.java
index 018e1c9..c6f1dd5 100644
---
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/EngineEJBUtilsStrategy.java
+++
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/ejb/EngineEJBUtilsStrategy.java
@@ -15,6 +15,7 @@
protected void addJNDIBeans() {
addBeanJNDIName(BeanType.BACKEND,
ENGINE_CONTEXT_PREFIX.concat("bll/Backend"));
addBeanJNDIName(BeanType.SCHEDULER,
ENGINE_CONTEXT_PREFIX.concat("scheduler/Scheduler"));
+ addBeanJNDIName(BeanType.PERSISTENT_SCHEDULER,
ENGINE_CONTEXT_PREFIX.concat("scheduler/PersistentScheduler"));
addBeanJNDIName(BeanType.VDS_EVENT_LISTENER,
ENGINE_CONTEXT_PREFIX.concat("bll/VdsEventListener"));
addBeanJNDIName(BeanType.LOCK_MANAGER,
ENGINE_CONTEXT_PREFIX.concat("bll/LockManager"));
addBeanJNDIName(BeanType.EVENTQUEUE_MANAGER,
ENGINE_CONTEXT_PREFIX.concat("bll/EventQueue"));
diff --git a/packaging/dbscripts/upgrade/03_05_1260_insert_quartz_tables.sql
b/packaging/dbscripts/upgrade/03_05_1260_insert_quartz_tables.sql
new file mode 100644
index 0000000..a7b6b4b
--- /dev/null
+++ b/packaging/dbscripts/upgrade/03_05_1260_insert_quartz_tables.sql
@@ -0,0 +1,175 @@
+-- Thanks to Patrick Lightbody for submitting this...
+--
+-- In your Quartz properties file, you'll need to set
+-- org.quartz.jobStore.driverDelegateClass =
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
+CREATE TABLE qrtz_job_details
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ job_name VARCHAR(200) NOT NULL,
+ job_group VARCHAR(200) NOT NULL,
+ description TEXT NULL,
+ job_class_name VARCHAR(250) NOT NULL,
+ is_durable BOOL NOT NULL,
+ is_nonconcurrent BOOL NOT NULL,
+ is_update_data BOOL NOT NULL,
+ requests_recovery BOOL NOT NULL,
+ job_data BYTEA NULL,
+ PRIMARY KEY (sched_name,job_name,job_group)
+);
+
+CREATE TABLE qrtz_triggers
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ trigger_name VARCHAR(200) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ job_name VARCHAR(200) NOT NULL,
+ job_group VARCHAR(200) NOT NULL,
+ description TEXT NULL,
+ next_fire_time BIGINT NULL,
+ prev_fire_time BIGINT NULL,
+ priority INTEGER NULL,
+ trigger_state VARCHAR(16) NOT NULL,
+ trigger_type VARCHAR(8) NOT NULL,
+ start_time BIGINT NOT NULL,
+ end_time BIGINT NULL,
+ calendar_name VARCHAR(200) NULL,
+ misfire_instr SMALLINT NULL,
+ job_data BYTEA NULL,
+ PRIMARY KEY (sched_name,trigger_name,trigger_group),
+ FOREIGN KEY (sched_name,job_name,job_group)
+ REFERENCES qrtz_job_details(sched_name,job_name,job_group)
+);
+
+CREATE TABLE qrtz_simple_triggers
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ trigger_name VARCHAR(200) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ repeat_count BIGINT NOT NULL,
+ repeat_interval BIGINT NOT NULL,
+ times_triggered BIGINT NOT NULL,
+ PRIMARY KEY (sched_name,trigger_name,trigger_group)
+);
+
+SELECT fn_db_create_constraint('qrtz_simple_triggers',
'fk_qrtz_simple_triggers_sched_name',
+'FOREIGN KEY (sched_name,trigger_name,trigger_group) REFERENCES
qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE');
+
+CREATE TABLE qrtz_cron_triggers
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ trigger_name VARCHAR(200) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ cron_expression VARCHAR(120) NOT NULL,
+ time_zone_id VARCHAR(80),
+ PRIMARY KEY (sched_name,trigger_name,trigger_group)
+);
+
+SELECT fn_db_create_constraint('qrtz_cron_triggers',
'fk_qrtz_cron_triggers_sched_name',
+'FOREIGN KEY (sched_name,trigger_name,trigger_group) REFERENCES
qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE');
+
+CREATE TABLE qrtz_simprop_triggers
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ trigger_name VARCHAR(200) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ str_prop_1 VARCHAR(512) NULL,
+ str_prop_2 VARCHAR(512) NULL,
+ str_prop_3 VARCHAR(512) NULL,
+ int_prop_1 INT NULL,
+ int_prop_2 INT NULL,
+ long_prop_1 BIGINT NULL,
+ long_prop_2 BIGINT NULL,
+ dec_prop_1 NUMERIC(13,4) NULL,
+ dec_prop_2 NUMERIC(13,4) NULL,
+ bool_prop_1 BOOL NULL,
+ bool_prop_2 BOOL NULL,
+ PRIMARY KEY (sched_name,trigger_name,trigger_group)
+);
+
+SELECT fn_db_create_constraint('qrtz_simprop_triggers',
'fk_qrtz_simprop_triggers_sched_name',
+'FOREIGN KEY (sched_name,trigger_name,trigger_group) REFERENCES
qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE');
+
+CREATE TABLE qrtz_blob_triggers
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ trigger_name VARCHAR(200) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ blob_data BYTEA NULL,
+ PRIMARY KEY (sched_name,trigger_name,trigger_group)
+);
+SELECT fn_db_create_constraint('qrtz_blob_triggers',
'fk_qrtz_blob_triggers_sched_name',
+'FOREIGN KEY (sched_name,trigger_name,trigger_group) REFERENCES
qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE');
+
+
+CREATE TABLE qrtz_calendars
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ calendar_name VARCHAR(200) NOT NULL,
+ calendar BYTEA NOT NULL,
+ PRIMARY KEY (sched_name,calendar_name)
+);
+
+
+CREATE TABLE qrtz_paused_trigger_grps
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ PRIMARY KEY (sched_name,trigger_group)
+);
+
+CREATE TABLE qrtz_fired_triggers
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ entry_id VARCHAR(95) NOT NULL,
+ trigger_name VARCHAR(200) NOT NULL,
+ trigger_group VARCHAR(200) NOT NULL,
+ instance_name VARCHAR(200) NOT NULL,
+ fired_time BIGINT NOT NULL,
+ sched_time BIGINT NULL,
+ priority INTEGER NOT NULL,
+ state VARCHAR(16) NOT NULL,
+ job_name VARCHAR(200) NULL,
+ job_group VARCHAR(200) NULL,
+ is_nonconcurrent BOOL NULL,
+ requests_recovery BOOL NULL,
+ PRIMARY KEY (sched_name,entry_id)
+);
+
+CREATE TABLE qrtz_scheduler_state
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ instance_name VARCHAR(200) NOT NULL,
+ last_checkin_time BIGINT NOT NULL,
+ checkin_interval BIGINT NOT NULL,
+ PRIMARY KEY (sched_name,instance_name)
+);
+
+CREATE TABLE qrtz_locks
+ (
+ sched_name VARCHAR(120) NOT NULL,
+ lock_name VARCHAR(40) NOT NULL,
+ PRIMARY KEY (sched_name,lock_name)
+);
+
+CREATE INDEX IDX_qrtz_j_req_recovery on
qrtz_job_details(sched_name,requests_recovery);
+CREATE INDEX IDX_qrtz_j_grp on qrtz_job_details(sched_name,job_group);
+
+CREATE INDEX IDX_qrtz_t_j on qrtz_triggers(sched_name,job_name,job_group);
+CREATE INDEX IDX_qrtz_t_jg on qrtz_triggers(sched_name,job_group);
+CREATE INDEX IDX_qrtz_t_c on qrtz_triggers(sched_name,calendar_name);
+CREATE INDEX IDX_qrtz_t_g on qrtz_triggers(sched_name,trigger_group);
+CREATE INDEX IDX_qrtz_t_state on qrtz_triggers(sched_name,trigger_state);
+CREATE INDEX IDX_qrtz_t_n_state on
qrtz_triggers(sched_name,trigger_name,trigger_group,trigger_state);
+CREATE INDEX IDX_qrtz_t_n_g_state on
qrtz_triggers(sched_name,trigger_group,trigger_state);
+CREATE INDEX IDX_qrtz_t_next_fire_time on
qrtz_triggers(sched_name,next_fire_time);
+CREATE INDEX IDX_qrtz_t_nft_st on
qrtz_triggers(sched_name,trigger_state,next_fire_time);
+CREATE INDEX IDX_qrtz_t_nft_misfire on
qrtz_triggers(sched_name,misfire_instr,next_fire_time);
+CREATE INDEX IDX_qrtz_t_nft_st_misfire on
qrtz_triggers(sched_name,misfire_instr,next_fire_time,trigger_state);
+CREATE INDEX IDX_qrtz_t_nft_st_misfire_grp on
qrtz_triggers(sched_name,misfire_instr,next_fire_time,trigger_group,trigger_state);
+
+CREATE INDEX IDX_qrtz_ft_trig_inst_name on
qrtz_fired_triggers(sched_name,instance_name);
+CREATE INDEX IDX_qrtz_ft_inst_job_req_rcvry on
qrtz_fired_triggers(sched_name,instance_name,requests_recovery);
+CREATE INDEX IDX_qrtz_ft_j_g on
qrtz_fired_triggers(sched_name,job_name,job_group);
+CREATE INDEX IDX_qrtz_ft_jg on qrtz_fired_triggers(sched_name,job_group);
+CREATE INDEX IDX_qrtz_ft_t_g on
qrtz_fired_triggers(sched_name,trigger_name,trigger_group);
+CREATE INDEX IDX_qrtz_ft_tg on qrtz_fired_triggers(sched_name,trigger_group);
diff --git a/packaging/services/ovirt-engine/ovirt-engine.conf.in
b/packaging/services/ovirt-engine/ovirt-engine.conf.in
index ef8b374..f0b604c 100644
--- a/packaging/services/ovirt-engine/ovirt-engine.conf.in
+++ b/packaging/services/ovirt-engine/ovirt-engine.conf.in
@@ -185,6 +185,13 @@
ENGINE_DB_MAX_CONNECTIONS=100
#
+# Size of the database connection pool for non-JTA
+# datasource used by Quartz
+#
+ENGINE_NON_JTA_DB_MIN_CONNECTIONS=1
+ENGINE_NON_JTA_DB_MAX_CONNECTIONS=10
+
+#
# Timeout value in milliseconds for stop checking if database
# connectivity is available (5 minutes at the moment):
#
diff --git a/packaging/services/ovirt-engine/ovirt-engine.xml.in
b/packaging/services/ovirt-engine/ovirt-engine.xml.in
index 06b1765..b62993c 100644
--- a/packaging/services/ovirt-engine/ovirt-engine.xml.in
+++ b/packaging/services/ovirt-engine/ovirt-engine.xml.in
@@ -163,6 +163,29 @@
</validation>
</datasource>
+ <datasource jndi-name="java:/ENGINEDataSourceNoJTA"
pool-name="ENGINEDataSourceNoJTA" enabled="true" use-ccm="false" jta="false">
+
<connection-url><![CDATA[$getstring('ENGINE_DB_URL')]]></connection-url>
+ <driver>postgresql</driver>
+
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
+ <pool>
+
<min-pool-size>$getinteger('ENGINE_NON_JTA_DB_MIN_CONNECTIONS')</min-pool-size>
+
<max-pool-size>$getinteger('ENGINE_NON_JTA_DB_MAX_CONNECTIONS')</max-pool-size>
+ <prefill>true</prefill>
+ </pool>
+ <security>
+ <user-name><![CDATA[$getstring('ENGINE_DB_USER')]]></user-name>
+ <password><![CDATA[$getstring('ENGINE_DB_PASSWORD')]]></password>
+ </security>
+ <statement>
+ <prepared-statement-cache-size>100</prepared-statement-cache-size>
+ <share-prepared-statements/>
+ </statement>
+ <validation>
+ <validate-on-match>true</validate-on-match>
+ <check-valid-connection-sql>select 1</check-valid-connection-sql>
+ </validation>
+ </datasource>
+
<drivers>
<driver name="postgresql" module="org.postgresql">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
--
To view, visit https://gerrit.ovirt.org/39273
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I9a34dac95999cb6b3721d201c116fb5f6089bb61
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: ovirt-engine-3.5-gluster
Gerrit-Owner: Shubhendu Tripathi <[email protected]>
Gerrit-Reviewer: Sahina Bose <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches