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

Reply via email to