/************************************************************************* * Copyright 2009-2013 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.objectstorage.bootstrap; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.DateBuilder; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerKey; import org.quartz.UnableToInterruptJobException; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.GroupMatcher; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.objectstorage.entities.ScheduledJob; import com.eucalyptus.objectstorage.jobs.LifecycleReaperJob; import com.eucalyptus.objectstorage.jobs.MainBucketReaperJob; import com.eucalyptus.objectstorage.jobs.MainObjectReaperJob; /* * */ public class ObjectStorageSchedulerManager { private static Logger LOG = Logger.getLogger(ObjectStorageSchedulerManager.class); static final String OSG_JOB_GROUP = "OsgJobs"; static final String LIFECYCLE_CLEANUP_CLASSNAME = LifecycleReaperJob.class.getName(); static final String LIFECYCLE_CLEANUP_DEFAULT_SCHEDULE = "0 0 1 * * ?"; static final String OBJECT_REAPER_CLASSNAME = MainObjectReaperJob.class.getName(); static final String OBJECT_REAPER_DEFAULT_SCHEDULE = "interval: 60"; static final String BUCKET_REAPER_CLASSNAME = MainBucketReaperJob.class.getName(); static final String BUCKET_REAPER_DEFAULT_SCHEDULE = "interval: 60"; private static Scheduler scheduler = null; private static final Lock lock = new ReentrantLock(true); private static boolean initted = false; static { lock.lock(); try { scheduler = StdSchedulerFactory.getDefaultScheduler(); if (!scheduler.isStarted()) { scheduler.start(); } initted = true; } catch (SchedulerException se) { LOG.error("quartz scheduler for ObjectStorageGateway jobs did not initialize properly, " + "exception caught with message - " + se.getMessage()); } finally { lock.unlock(); } } public static void start() { lock.lock(); try { if (!initted) { LOG.error("cannot start quartz scheduler for ObjectStorageGateway because it is not initialized"); } else { populateJobs(); } } catch (Exception e) { LOG.error("caught an exception while attempting to start the quartz scheduler for ObjectStorageGateway jobs, " + "the exception had the message - " + e.getMessage()); } finally { lock.unlock(); } } public static void stop() { lock.lock(); try { if (!initted) { LOG.error("cannot shutdown quartz scheduler for ObjectStorageGateway because it is not initialized"); } else { Set<JobKey> osgJobKeys = scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(OSG_JOB_GROUP)); if (osgJobKeys != null && osgJobKeys.size() > 0) { for (JobKey jobKey : osgJobKeys) { scheduler.deleteJob(jobKey); } } List<JobExecutionContext> runningJobs = scheduler.getCurrentlyExecutingJobs(); if (runningJobs != null && runningJobs.size() > 0) { for (JobExecutionContext ctx : runningJobs) { try { scheduler.interrupt(ctx.getFireInstanceId()); } catch (UnableToInterruptJobException utije) { LOG.warn("unable to interrupt job - " + ctx.getJobDetail().getJobClass().getName() + ", received an UnableToInterruptJobException which was caught so it " + "does not bubble up"); } } } } } catch (Exception e) { LOG.error("caught an exception while attempting to shutdown the quartz scheduler for ObjectStorageGateway jobs, " + "the exception had the message - " + e.getMessage()); } finally { lock.unlock(); } } public static String dumpInfo() { StringBuilder sb = new StringBuilder(); try { sb.append("scheduler is named " + scheduler.getSchedulerName() + "\n"); if (scheduler.isStarted()) { sb.append("scheduler is started\n"); if (scheduler.getJobGroupNames() != null && scheduler.getJobGroupNames().size() > 0) { sb.append("scheduler has " + scheduler.getJobGroupNames().size() + " job groups\n"); for (String groupName : scheduler.getJobGroupNames()) { Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(groupName)); if (jobKeys != null && jobKeys.size() > 0) { sb.append(" in job group " + groupName + " there are " + jobKeys.size() + " jobs\n"); for (JobKey jobKey : jobKeys) { JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail != null) { sb.append(" job named " + jobKey.getName() + " using class " + jobDetail.getJobClass().getName() + "\n"); List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); if (triggers != null && triggers.size() > 0) { for (Trigger trigger : triggers) { sb.append(" " + jobKey.getName() + " is triggered by " + trigger.getKey().getName() + "\n"); } } } } } } } else { sb.append("scheduler does not have any job groups\n"); } if (scheduler.getTriggerGroupNames() != null && scheduler.getTriggerGroupNames().size() > 0) { sb.append("scheduler has " + scheduler.getTriggerGroupNames().size() + " trigger groups\n"); for (String triggerGroupName : scheduler.getTriggerGroupNames()) { Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.<TriggerKey>groupEquals(triggerGroupName)); if (triggerKeys != null && triggerKeys.size() > 0) { sb.append(" in trigger group " + triggerGroupName + " there are " + triggerKeys.size() + " triggers\n"); for (TriggerKey triggerKey : triggerKeys) { sb.append(" trigger named " + triggerKey.getName() + " is currently in state " + scheduler.getTriggerState(triggerKey).name() + " and will fire again at " + scheduler.getTrigger(triggerKey).getNextFireTime().toString() + "\n"); } } } } else { sb.append("scheduler does not have any trigger groups\n"); } } else { sb.append("scheduler is shutdown\n"); } } catch (SchedulerException se) { sb.append("exception caught while dumping scheduler info - " + se.getMessage() + "\n"); } return sb.toString(); } private static boolean checkForLifecycleCleanupJob(String jobName) { return checkForJobByName(jobName, LIFECYCLE_CLEANUP_CLASSNAME); } private static boolean checkForDeletedObjectReaperJob(String jobName) { return checkForJobByName(jobName, OBJECT_REAPER_CLASSNAME); } private static boolean checkForDeletedBucketReaperJob(String jobName) { return checkForJobByName(jobName, BUCKET_REAPER_CLASSNAME); } private static boolean checkForJobByName(String checking, String checkAgainst) { if (checking != null && checking.equals(checkAgainst)) { return true; } return false; } private static void populateJobs() { boolean foundLifecycleCleanup = false; boolean foundDeletedObjectReaper = false; boolean foundDeletedBucketReaper = false; List<ScheduledJob> jobs = null; try (TransactionResource tran = Entities.transactionFor(ScheduledJob.class)) { jobs = Entities.query(new ScheduledJob()); tran.commit(); } catch (Exception ex) { LOG.error("exception encountered while populating ObjectStorageGateway scheduled jobs from database - " + ex.getMessage()); } int jobIdx = 1; if (jobs != null && jobs.size() > 0) { for (ScheduledJob job : jobs) { scheduleJobInQuartz(job.getJobClassName(), job.getJobSchedule(), jobIdx); jobIdx++; if (!foundLifecycleCleanup) { foundLifecycleCleanup = checkForLifecycleCleanupJob(job.getJobClassName()); } if (!foundDeletedObjectReaper) { foundDeletedObjectReaper = checkForDeletedObjectReaperJob(job.getJobClassName()); } if (!foundDeletedBucketReaper) { foundDeletedBucketReaper = checkForDeletedBucketReaperJob(job.getJobClassName()); } } } else { LOG.debug("jobs were either not found in the database, or an exception occurred while querying " + "for scheduled jobs"); } if (!foundDeletedObjectReaper) { saveJobToDb(OBJECT_REAPER_CLASSNAME, OBJECT_REAPER_DEFAULT_SCHEDULE, "performs upstream deletes of objects that have been deleted in the object storage gateway"); scheduleJobInQuartz(OBJECT_REAPER_CLASSNAME, OBJECT_REAPER_DEFAULT_SCHEDULE, jobIdx); jobIdx++; } if (!foundDeletedBucketReaper) { saveJobToDb(BUCKET_REAPER_CLASSNAME, BUCKET_REAPER_DEFAULT_SCHEDULE, "performs upstream deletes of buckets that have been deleted in the object storage gateway"); scheduleJobInQuartz(BUCKET_REAPER_CLASSNAME, BUCKET_REAPER_DEFAULT_SCHEDULE, jobIdx); jobIdx++; } if (!foundLifecycleCleanup) { saveJobToDb(LIFECYCLE_CLEANUP_CLASSNAME, LIFECYCLE_CLEANUP_DEFAULT_SCHEDULE, "processes bucket lifecycle rules"); scheduleJobInQuartz(LIFECYCLE_CLEANUP_CLASSNAME, LIFECYCLE_CLEANUP_DEFAULT_SCHEDULE, jobIdx); jobIdx++; } } private static void saveJobToDb(String jobClassName, String schedule, String description) { ScheduledJob job = new ScheduledJob(); job.setJobSchedule(schedule); job.setJobClassName(jobClassName); job.setJobDescription(description); try (TransactionResource tran = Entities.transactionFor(ScheduledJob.class)) { Entities.persist(job); tran.commit(); } catch (Exception ex) { LOG.warn("an exception was thrown while saving record to scheduled_jobs table for job_class_name - " + jobClassName + " the error message is - " + ex.getMessage() + ". Ignoring so that the job can be scheduled in quartz"); } } private static void scheduleJobInQuartz(String jobClassName, String schedule, int jobIdx) { Class jobClass = null; try { jobClass = Class.forName(jobClassName); } catch (ClassNotFoundException e) { LOG.error("attempting to add job of type " + jobClassName + ", but the class was not found, job will not be added"); } if (jobClass != null) { // make job try { JobDetail jobDetail = newJob(jobClass).withIdentity("job" + jobIdx, OSG_JOB_GROUP).build(); if (schedule != null && schedule.startsWith("interval:")) { String jobSchedule = schedule.substring("interval:".length(), schedule.length()); int intervalSecs; try { intervalSecs = Integer.parseInt(jobSchedule.trim()); Trigger trigger = newTrigger().withIdentity("trigger" + jobIdx, OSG_JOB_GROUP) .withSchedule(simpleSchedule().withIntervalInSeconds(intervalSecs).repeatForever()).startAt( // using intervalSecs + jobIdx to spread out job starts by a small amount futureDate(intervalSecs + jobIdx, DateBuilder.IntervalUnit.SECOND)).build(); scheduler.scheduleJob(jobDetail, trigger); LOG.info("job named job" + jobIdx + " added to group " + OSG_JOB_GROUP + " using class " + jobClassName + " with interval schedule '" + schedule + "'"); } catch (NumberFormatException nfe) { LOG.error("failed to schedule job with class name - " + jobClassName + " because the interval - " + jobSchedule + " failed to be parsed as an integer"); } } else { CronTrigger trigger = newTrigger().withIdentity("trigger" + jobIdx, OSG_JOB_GROUP).withSchedule(CronScheduleBuilder.cronSchedule(schedule)).build(); scheduler.scheduleJob(jobDetail, trigger); LOG.info("job named job" + jobIdx + " added to group " + OSG_JOB_GROUP + " using class " + jobClassName + " with cron schedule '" + schedule + "'"); } } catch (SchedulerException se) { LOG.error("while attempting to schedule job using class " + jobClassName + " an exception occurred with message - " + se.getMessage()); } } else { LOG.error("job was not scheduled because class " + jobClassName + " was either not found or invalid"); } } }