/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.aurora.scheduler.cron.quartz;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Singleton;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import org.apache.aurora.common.args.Arg;
import org.apache.aurora.common.args.CmdLine;
import org.apache.aurora.common.args.constraints.Positive;
import org.apache.aurora.common.quantity.Amount;
import org.apache.aurora.common.quantity.Time;
import org.apache.aurora.common.util.BackoffHelper;
import org.apache.aurora.scheduler.cron.CronJobManager;
import org.apache.aurora.scheduler.cron.CronPredictor;
import org.apache.aurora.scheduler.cron.CronScheduler;
import org.apache.aurora.scheduler.cron.quartz.AuroraCronJob.CronBatchWorker;
import org.apache.aurora.scheduler.events.PubsubEventModule;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.simpl.SimpleThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.aurora.scheduler.SchedulerServicesModule.addSchedulerActiveServiceBinding;
import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_INSTANCE_ID;
import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON;
import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_NAME;
import static org.quartz.impl.StdSchedulerFactory.PROP_THREAD_POOL_CLASS;
import static org.quartz.impl.StdSchedulerFactory.PROP_THREAD_POOL_PREFIX;
/**
* Provides a {@link CronJobManager} with a Quartz backend. While Quartz itself supports
* persistence, the scheduler exposed by this module does not persist any state - it simply
* creates tasks from a {@link org.apache.aurora.gen.JobConfiguration} template on a cron-style
* schedule.
*/
public class CronModule extends AbstractModule {
private static final Logger LOG = LoggerFactory.getLogger(CronModule.class);
@CmdLine(name = "cron_scheduler_num_threads",
help = "Number of threads to use for the cron scheduler thread pool.")
private static final Arg<Integer> NUM_THREADS = Arg.create(10);
@CmdLine(name = "cron_timezone", help = "TimeZone to use for cron predictions.")
private static final Arg<String> CRON_TIMEZONE = Arg.create("GMT");
@CmdLine(name = "cron_start_initial_backoff", help =
"Initial backoff delay while waiting for a previous cron run to be killed.")
public static final Arg<Amount<Long, Time>> CRON_START_INITIAL_BACKOFF =
Arg.create(Amount.of(5L, Time.SECONDS));
@CmdLine(name = "cron_start_max_backoff", help =
"Max backoff delay while waiting for a previous cron run to be killed.")
public static final Arg<Amount<Long, Time>> CRON_START_MAX_BACKOFF =
Arg.create(Amount.of(1L, Time.MINUTES));
@Positive
@CmdLine(name = "cron_scheduling_max_batch_size",
help = "The maximum number of triggered cron jobs that can be processed in a batch.")
private static final Arg<Integer> CRON_MAX_BATCH_SIZE = Arg.create(10);
// Global per-JVM ID number generator for the provided Quartz Scheduler.
private static final AtomicLong ID_GENERATOR = new AtomicLong();
@Override
protected void configure() {
bind(CronPredictor.class).to(CronPredictorImpl.class);
bind(CronPredictorImpl.class).in(Singleton.class);
bind(CronJobManager.class).to(CronJobManagerImpl.class);
bind(CronJobManagerImpl.class).in(Singleton.class);
bind(CronScheduler.class).to(CronSchedulerImpl.class);
bind(CronSchedulerImpl.class).in(Singleton.class);
bind(AuroraCronJobFactory.class).in(Singleton.class);
bind(AuroraCronJob.class).in(Singleton.class);
bind(AuroraCronJob.Config.class).toInstance(new AuroraCronJob.Config(
new BackoffHelper(CRON_START_INITIAL_BACKOFF.get(), CRON_START_MAX_BACKOFF.get())));
PubsubEventModule.bindSubscriber(binder(), AuroraCronJob.class);
bind(CronLifecycle.class).in(Singleton.class);
addSchedulerActiveServiceBinding(binder()).to(CronLifecycle.class);
bind(new TypeLiteral<Integer>() { })
.annotatedWith(AuroraCronJob.CronMaxBatchSize.class)
.toInstance(CRON_MAX_BATCH_SIZE.get());
bind(CronBatchWorker.class).in(Singleton.class);
addSchedulerActiveServiceBinding(binder()).to(CronBatchWorker.class);
}
@Provides
TimeZone provideTimeZone() {
TimeZone timeZone = TimeZone.getTimeZone(CRON_TIMEZONE.get());
TimeZone systemTimeZone = TimeZone.getDefault();
if (!timeZone.equals(systemTimeZone)) {
LOG.warn("Cron schedules are configured to fire according to timezone "
+ timeZone.getDisplayName()
+ " but system timezone is set to "
+ systemTimeZone.getDisplayName());
}
return timeZone;
}
@Provides
@Singleton
static Scheduler provideScheduler(AuroraCronJobFactory jobFactory) throws SchedulerException {
// There are several ways to create a quartz Scheduler instance. This path was chosen as the
// simplest to create a Scheduler that uses a *daemon* QuartzSchedulerThread instance.
Properties props = new Properties();
String name = "aurora-cron-" + ID_GENERATOR.incrementAndGet();
props.setProperty(PROP_SCHED_NAME, name);
props.setProperty(PROP_SCHED_INSTANCE_ID, name);
props.setProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getCanonicalName());
props.setProperty(PROP_THREAD_POOL_PREFIX + ".threadCount", NUM_THREADS.get().toString());
props.setProperty(PROP_THREAD_POOL_PREFIX + ".makeThreadsDaemons", Boolean.TRUE.toString());
props.setProperty(PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON, Boolean.TRUE.toString());
Scheduler scheduler = new StdSchedulerFactory(props).getScheduler();
scheduler.setJobFactory(jobFactory);
return scheduler;
}
}