You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by ke...@apache.org on 2014/04/24 03:18:21 UTC
[5/5] git commit: Revert "AURORA-132: Cron system based on Quartz"
Revert "AURORA-132: Cron system based on Quartz"
This reverts commit c285f2f83dc769143bc4a3be67e3d53b8fc51418.
Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/5de2b1ab
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/5de2b1ab
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/5de2b1ab
Branch: refs/heads/master
Commit: 5de2b1ab36ca776f684d3fb6784499ac8490e9a2
Parents: 53dc494
Author: Kevin Sweeney <ke...@apache.org>
Authored: Wed Apr 23 18:17:53 2014 -0700
Committer: Kevin Sweeney <ke...@apache.org>
Committed: Wed Apr 23 18:17:53 2014 -0700
----------------------------------------------------------------------
build.gradle | 1 -
.../aurora/scheduler/MesosTaskFactory.java | 2 +-
.../aurora/scheduler/app/SchedulerMain.java | 20 +-
.../aurora/scheduler/async/TaskGroups.java | 2 +-
.../apache/aurora/scheduler/base/JobKeys.java | 40 +-
.../configuration/ConfigurationManager.java | 12 +-
.../aurora/scheduler/cron/CronJobManager.java | 97 -
.../aurora/scheduler/cron/CronPredictor.java | 2 +-
.../aurora/scheduler/cron/CronScheduler.java | 40 +-
.../aurora/scheduler/cron/CrontabEntryTest.java | 163 +
.../aurora/scheduler/cron/SanitizedCronJob.java | 131 -
.../scheduler/cron/noop/NoopCronModule.java | 40 +
.../scheduler/cron/noop/NoopCronPredictor.java | 33 +
.../scheduler/cron/noop/NoopCronScheduler.java | 83 +
.../scheduler/cron/quartz/AuroraCronJob.java | 231 --
.../cron/quartz/AuroraCronJobFactory.java | 49 -
.../cron/quartz/CronJobManagerImpl.java | 256 --
.../scheduler/cron/quartz/CronLifecycle.java | 114 -
.../scheduler/cron/quartz/CronModule.java | 130 -
.../cron/quartz/CronPredictorImpl.java | 46 -
.../cron/quartz/CronSchedulerImpl.java | 71 -
.../aurora/scheduler/cron/quartz/Quartz.java | 124 -
.../scheduler/cron/testing/AbstractCronIT.java | 135 +
.../org/apache/aurora/scheduler/http/Cron.java | 3 +-
.../aurora/scheduler/http/ServletModule.java | 2 +-
.../aurora/scheduler/http/StructDump.java | 15 +-
.../aurora/scheduler/state/CronJobManager.java | 484 +++
.../aurora/scheduler/state/LockManagerImpl.java | 2 +-
.../scheduler/state/SchedulerCoreImpl.java | 30 +-
.../aurora/scheduler/state/StateModule.java | 7 +
.../thrift/SchedulerThriftInterface.java | 40 +-
.../cron/testing/cron-schedule-predictions.json | 3332 ++++++++++++++++++
.../aurora/scheduler/cron/CrontabEntryTest.java | 168 -
.../scheduler/cron/ExpectedPrediction.java | 55 -
.../aurora/scheduler/cron/noop/NoopCronIT.java | 63 +
.../cron/quartz/AuroraCronJobTest.java | 174 -
.../aurora/scheduler/cron/quartz/CronIT.java | 257 --
.../cron/quartz/CronJobManagerImplTest.java | 221 --
.../cron/quartz/CronPredictorImplTest.java | 89 -
.../scheduler/cron/quartz/QuartzTestUtil.java | 78 -
.../state/BaseSchedulerCoreImplTest.java | 344 +-
.../scheduler/state/CronJobManagerTest.java | 490 +++
.../scheduler/state/LockManagerImplTest.java | 2 +-
.../thrift/SchedulerThriftInterfaceTest.java | 17 +-
.../aurora/scheduler/thrift/ThriftIT.java | 4 +-
.../scheduler/cron/expected-predictions.json | 3332 ------------------
46 files changed, 5223 insertions(+), 5808 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
index 831d8b3..459cd85 100644
--- a/build.gradle
+++ b/build.gradle
@@ -163,7 +163,6 @@ dependencies {
compile 'org.apache.mesos:mesos:0.17.0'
compile thriftLib
compile 'org.apache.zookeeper:zookeeper:3.3.4'
- compile 'org.quartz-scheduler:quartz:2.2.1'
compile "org.slf4j:slf4j-api:${slf4jRev}"
compile "org.slf4j:slf4j-jdk14:${slf4jRev}"
compile 'com.twitter.common.logging:log4j:0.0.7'
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java b/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
index bdd8c19..86bbc29 100644
--- a/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
+++ b/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
@@ -135,7 +135,7 @@ public interface MesosTaskFactory {
}
TaskInfo.Builder taskBuilder =
TaskInfo.newBuilder()
- .setName(JobKeys.canonicalString(Tasks.ASSIGNED_TO_JOB_KEY.apply(task)))
+ .setName(JobKeys.toPath(Tasks.ASSIGNED_TO_JOB_KEY.apply(task)))
.setTaskId(TaskID.newBuilder().setValue(task.getTaskId()))
.setSlaveId(slaveId)
.addAllResources(resources)
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java b/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
index da6f5e5..bf3d7a3 100644
--- a/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
+++ b/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
@@ -59,7 +59,9 @@ import org.apache.aurora.scheduler.DriverFactory;
import org.apache.aurora.scheduler.DriverFactory.DriverFactoryImpl;
import org.apache.aurora.scheduler.MesosTaskFactory.ExecutorConfig;
import org.apache.aurora.scheduler.SchedulerLifecycle;
-import org.apache.aurora.scheduler.cron.quartz.CronModule;
+import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CronScheduler;
+import org.apache.aurora.scheduler.cron.noop.NoopCronModule;
import org.apache.aurora.scheduler.local.IsolatedSchedulerModule;
import org.apache.aurora.scheduler.log.mesos.MesosLogStreamModule;
import org.apache.aurora.scheduler.storage.backup.BackupModule;
@@ -113,7 +115,17 @@ public class SchedulerMain extends AbstractApplication {
.add(CapabilityValidator.class)
.build();
- // TODO(Suman Karumuri): Pass in AUTH as extra module
+ @CmdLine(name = "cron_module",
+ help = "A Guice module to provide cron bindings. NOTE: The default is a no-op.")
+ private static final Arg<? extends Class<? extends Module>> CRON_MODULE =
+ Arg.create(NoopCronModule.class);
+
+ private static final Iterable<Class<?>> CRON_MODULE_CLASSES = ImmutableList.<Class<?>>builder()
+ .add(CronPredictor.class)
+ .add(CronScheduler.class)
+ .build();
+
+ // TODO(Suman Karumuri): Pass in AUTH and CRON modules as extra modules
@CmdLine(name = "extra_modules",
help = "A list of modules that provide additional functionality.")
private static final Arg<List<Class<? extends Module>>> EXTRA_MODULES =
@@ -139,7 +151,8 @@ public class SchedulerMain extends AbstractApplication {
private static Iterable<? extends Module> getExtraModules() {
Builder<Module> modules = ImmutableList.builder();
- modules.add(Modules.wrapInPrivateModule(AUTH_MODULE.get(), AUTH_MODULE_CLASSES));
+ modules.add(Modules.wrapInPrivateModule(AUTH_MODULE.get(), AUTH_MODULE_CLASSES))
+ .add(Modules.wrapInPrivateModule(CRON_MODULE.get(), CRON_MODULE_CLASSES));
for (Class<? extends Module> moduleClass : EXTRA_MODULES.get()) {
modules.add(Modules.getModule(moduleClass));
@@ -160,7 +173,6 @@ public class SchedulerMain extends AbstractApplication {
.addAll(getExtraModules())
.add(new LogStorageModule())
.add(new MemStorageModule(Bindings.annotatedKeyFactory(LogStorage.WriteBehind.class)))
- .add(new CronModule())
.add(new ThriftModule())
.add(new ThriftAuthModule())
.build();
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java b/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
index ef73032..6aff091 100644
--- a/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
+++ b/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
@@ -243,7 +243,7 @@ public class TaskGroups implements EventSubscriber {
@Override
public String toString() {
- return JobKeys.canonicalString(Tasks.INFO_TO_JOB_KEY.apply(canonicalTask));
+ return JobKeys.toPath(Tasks.INFO_TO_JOB_KEY.apply(canonicalTask));
}
}
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java b/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
index c81ac62..db1bec4 100644
--- a/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
+++ b/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
@@ -15,20 +15,17 @@
*/
package org.apache.aurora.scheduler.base;
-import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Functions;
-import com.google.common.base.Joiner;
import com.google.common.base.Optional;
-import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import org.apache.aurora.gen.JobKey;
import org.apache.aurora.gen.TaskQuery;
-import org.apache.aurora.scheduler.configuration.ConfigurationManager;
import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
import org.apache.aurora.scheduler.storage.entities.IJobKey;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
@@ -86,9 +83,9 @@ public final class JobKeys {
*/
public static boolean isValid(@Nullable IJobKey jobKey) {
return jobKey != null
- && ConfigurationManager.isGoodIdentifier(jobKey.getRole())
- && ConfigurationManager.isGoodIdentifier(jobKey.getEnvironment())
- && ConfigurationManager.isGoodIdentifier(jobKey.getName());
+ && !Strings.isNullOrEmpty(jobKey.getRole())
+ && !Strings.isNullOrEmpty(jobKey.getEnvironment())
+ && !Strings.isNullOrEmpty(jobKey.getName());
}
/**
@@ -135,32 +132,25 @@ public final class JobKeys {
}
/**
- * Create a "/"-delimited representation of job key usable as a unique identifier in this cluster.
+ * Create a "/"-delimited String representation of a job key, suitable for logging but not
+ * necessarily suitable for use as a unique identifier.
*
- * It is guaranteed that {@code k.equals(JobKeys.parse(JobKeys.canonicalString(k))}.
- *
- * @see #parse(String)
* @param jobKey Key to represent.
- * @return Canonical "/"-delimited representation of the key.
+ * @return "/"-delimited representation of the key.
*/
- public static String canonicalString(IJobKey jobKey) {
- return Joiner.on("/").join(jobKey.getRole(), jobKey.getEnvironment(), jobKey.getName());
+ public static String toPath(IJobKey jobKey) {
+ return jobKey.getRole() + "/" + jobKey.getEnvironment() + "/" + jobKey.getName();
}
/**
- * Create a job key from a "role/environment/name" representation.
- *
- * It is guaranteed that {@code k.equals(JobKeys.parse(JobKeys.canonicalString(k))}.
+ * Create a "/"-delimited String representation of job key, suitable for logging but not
+ * necessarily suitable for use as a unique identifier.
*
- * @see #canonicalString(IJobKey)
- * @param string Input to parse.
- * @return Parsed representation.
- * @throws IllegalArgumentException when the string fails to parse.
+ * @param job Job to represent.
+ * @return "/"-delimited representation of the job's key.
*/
- public static IJobKey parse(String string) throws IllegalArgumentException {
- List<String> components = Splitter.on("/").splitToList(string);
- checkArgument(components.size() == 3);
- return from(components.get(0), components.get(1), components.get(2));
+ public static String toPath(IJobConfiguration job) {
+ return toPath(job.getKey());
}
/**
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
index e5ad461..82034e0 100644
--- a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
+++ b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
@@ -164,15 +164,9 @@ public final class ConfigurationManager {
// Utility class.
}
- /**
- * Verifies that an identifier is an acceptable name component.
- *
- * @param identifier Identifier to check.
- * @return false if the identifier is null or invalid.
- */
- public static boolean isGoodIdentifier(@Nullable String identifier) {
- return identifier != null
- && GOOD_IDENTIFIER.matcher(identifier).matches()
+ @VisibleForTesting
+ static boolean isGoodIdentifier(String identifier) {
+ return GOOD_IDENTIFIER.matcher(identifier).matches()
&& (identifier.length() <= MAX_IDENTIFIER_LENGTH);
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java b/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java
deleted file mode 100644
index 7c8d5ec..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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;
-
-import java.util.Map;
-
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-
-/**
- * Manages the persistence and scheduling of jobs that should be run periodically on a cron
- * schedule.
- */
-public interface CronJobManager {
- /**
- * Triggers execution of a job.
- *
- * @param jobKey Key of the job to start.
- * @throws CronException If the job could not be started with the cron system.
- */
- void startJobNow(IJobKey jobKey) throws CronException;
-
- /**
- * Persist a new cron job to storage and schedule it for future execution.
- *
- * @param config Cron job configuration to update to.
- * @throws CronException If a job with the same key does not exist or the job could not be
- * scheduled.
- */
- void updateJob(SanitizedCronJob config) throws CronException;
-
- /**
- * Persist a cron job to storage and schedule it for future execution.
- *
- * @param config New cron job configuration.
- * @throws CronException If a job with the same key exists or the job could not be scheduled.
- */
- void createJob(SanitizedCronJob config) throws CronException;
-
- /**
- * Get all cron jobs.
- *
- * TODO(ksweeney): Consider deprecating this and letting caller query storage directly.
- *
- * @return An immutable snapshot of cron jobs at some instant.
- */
- Iterable<IJobConfiguration> getJobs();
-
- /**
- * Test whether a job exists.
- *
- * TODO(ksweeney): Consider deprecating this and letting caller query storage directly.
- *
- * @param jobKey Key of the job to check.
- * @return false when a job does not exist in storage.
- */
- boolean hasJob(IJobKey jobKey);
-
- /**
- * Remove a job and deschedule it.
- *
- * @param jobKey Key of the job to delete.
- * @return true if a job was removed.
- */
- boolean deleteJob(IJobKey jobKey);
-
- /**
- * A list of the currently scheduled jobs and when they will run according to the underlying
- * execution engine.
- *
- * @return A map from job to the cron schedule in use for that job.
- */
- Map<IJobKey, CrontabEntry> getScheduledJobs();
-
- /**
- * The unique ID of this cron job manager, used as a prefix in the JobStore.
- *
- * TODO(ksweeney): Consider removing this from storage entirely since the JobManager abstraction
- * is gone.
- *
- * @return The unique ID of the manager.
- */
- String getManagerKey();
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java b/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
index 0ce60f8..df0c378 100644
--- a/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
+++ b/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
@@ -27,5 +27,5 @@ public interface CronPredictor {
* @param schedule Cron schedule to predict the next time for.
* @return A prediction for the next time a cron will run.
*/
- Date predictNextRun(CrontabEntry schedule);
+ Date predictNextRun(String schedule);
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java b/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
index f38dea5..56e9950 100644
--- a/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
+++ b/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
@@ -15,19 +15,49 @@
*/
package org.apache.aurora.scheduler.cron;
-import com.google.common.base.Optional;
+import javax.annotation.Nullable;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Service;
/**
* An execution manager that executes work on a cron schedule.
*/
-public interface CronScheduler {
+public interface CronScheduler extends Service {
+ /**
+ * Schedules a task on a cron schedule.
+ *
+ * @param schedule Cron-style schedule.
+ * @param task Work to run when on the cron schedule.
+ * @return A unique ID to identify the scheduled cron task.
+ * @throws CronException when there was a failure to schedule, for example if {@code schedule}
+ * is not a valid input.
+ * @throws IllegalStateException If the cron scheduler is not currently running.
+ */
+ String schedule(String schedule, Runnable task) throws CronException, IllegalStateException;
+
+ /**
+ * Removes a scheduled cron item.
+ *
+ * @param key Key previously returned from {@link #schedule(String, Runnable)}.
+ * @throws IllegalStateException If the cron scheduler is not currently running.
+ */
+ void deschedule(String key) throws IllegalStateException;
+
/**
* Gets the cron schedule associated with a scheduling key.
*
- * @param key Key previously returned from {@link #schedule(CrontabEntry, Runnable)}.
+ * @param key Key previously returned from {@link #schedule(String, Runnable)}.
* @return The task's cron schedule, if a matching task was found.
+ * @throws IllegalStateException If the cron scheduler is not currently running.
+ */
+ Optional<String> getSchedule(String key) throws IllegalStateException;
+
+ /**
+ * Checks to see if the scheduler would be accepted by the underlying scheduler.
+ *
+ * @param schedule Cron scheduler to validate.
+ * @return {@code true} if the schedule is valid.
*/
- Optional<CrontabEntry> getSchedule(IJobKey key);
+ boolean isValidSchedule(@Nullable String schedule);
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java b/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
new file mode 100644
index 0000000..2bb848a
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * 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;
+
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class CrontabEntryTest {
+ @Test
+ public void testHashCodeAndEquals() {
+
+ List<CrontabEntry> entries = ImmutableList.of(
+ CrontabEntry.parse("* * * * *"),
+ CrontabEntry.parse("0-59 * * * *"),
+ CrontabEntry.parse("0-57,58,59 * * * *"),
+ CrontabEntry.parse("* 23,1,2,4,0-22 * * *"),
+ CrontabEntry.parse("1-50,0,51-59 * * * sun-sat"));
+
+ for (CrontabEntry lhs : entries) {
+ for (CrontabEntry rhs : entries) {
+ assertEquals(lhs, rhs);
+ }
+ }
+
+ Set<CrontabEntry> equivalentEntries = Sets.newHashSet(entries);
+ assertTrue(equivalentEntries.size() == 1);
+ }
+
+ @Test
+ public void testEqualsCoverage() {
+ assertNotEquals(CrontabEntry.parse("* * * * *"), new Object());
+
+ assertNotEquals(CrontabEntry.parse("* * * * *"), CrontabEntry.parse("1 * * * *"));
+ assertEquals(CrontabEntry.parse("1,2,3 * * * *"), CrontabEntry.parse("1-3 * * * *"));
+
+ assertNotEquals(CrontabEntry.parse("* 0-22 * * *"), CrontabEntry.parse("* * * * *"));
+ assertEquals(CrontabEntry.parse("* 0-23 * * *"), CrontabEntry.parse("* * * * *"));
+
+ assertNotEquals(CrontabEntry.parse("1 1 1-30 * *"), CrontabEntry.parse("1 1 * * *"));
+ assertEquals(CrontabEntry.parse("1 1 1-31 * *"), CrontabEntry.parse("1 1 * * *"));
+
+ assertNotEquals(CrontabEntry.parse("1 1 * JAN,FEB-NOV *"), CrontabEntry.parse("1 1 * * *"));
+ assertEquals(CrontabEntry.parse("1 1 * JAN,FEB-DEC *"), CrontabEntry.parse("1 1 * * *"));
+
+ assertNotEquals(CrontabEntry.parse("* * * * SUN"), CrontabEntry.parse("* * * * SAT"));
+ assertEquals(CrontabEntry.parse("* * * * 0"), CrontabEntry.parse("* * * * SUN"));
+ }
+
+ @Test
+ public void testSkip() {
+ assertEquals(CrontabEntry.parse("*/15 * * * *"), CrontabEntry.parse("0,15,30,45 * * * *"));
+ assertEquals(
+ CrontabEntry.parse("* */2 * * *"),
+ CrontabEntry.parse("0-59 0,2,4,6,8,10,12-23/2 * * *"));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0-58 * * * *", CrontabEntry.parse("0,1-57,58 * * * *").toString());
+ assertEquals("* * * * *", CrontabEntry.parse("* * * * *").toString());
+ }
+
+ @Test
+ public void testWildcards() {
+ CrontabEntry wildcardMinuteEntry = CrontabEntry.parse("* 1 1 1 *");
+ assertEquals("*", wildcardMinuteEntry.getMinuteAsString());
+ assertTrue(wildcardMinuteEntry.hasWildcardMinute());
+ assertFalse(wildcardMinuteEntry.hasWildcardHour());
+ assertFalse(wildcardMinuteEntry.hasWildcardDayOfMonth());
+ assertFalse(wildcardMinuteEntry.hasWildcardMonth());
+ assertTrue(wildcardMinuteEntry.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardHourEntry = CrontabEntry.parse("1 * 1 1 *");
+ assertEquals("*", wildcardHourEntry.getHourAsString());
+ assertFalse(wildcardHourEntry.hasWildcardMinute());
+ assertTrue(wildcardHourEntry.hasWildcardHour());
+ assertFalse(wildcardHourEntry.hasWildcardDayOfMonth());
+ assertFalse(wildcardHourEntry.hasWildcardMonth());
+ assertTrue(wildcardHourEntry.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardDayOfMonth = CrontabEntry.parse("1 1 * 1 *");
+ assertEquals("*", wildcardDayOfMonth.getDayOfMonthAsString());
+ assertFalse(wildcardDayOfMonth.hasWildcardMinute());
+ assertFalse(wildcardDayOfMonth.hasWildcardHour());
+ assertTrue(wildcardDayOfMonth.hasWildcardDayOfMonth());
+ assertFalse(wildcardDayOfMonth.hasWildcardMonth());
+ assertTrue(wildcardDayOfMonth.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardMonth = CrontabEntry.parse("1 1 1 * *");
+ assertEquals("*", wildcardMonth.getMonthAsString());
+ assertFalse(wildcardMonth.hasWildcardMinute());
+ assertFalse(wildcardMonth.hasWildcardHour());
+ assertFalse(wildcardMonth.hasWildcardDayOfMonth());
+ assertTrue(wildcardMonth.hasWildcardMonth());
+ assertTrue(wildcardMonth.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardDayOfWeek = CrontabEntry.parse("1 1 1 1 *");
+ assertEquals("*", wildcardDayOfWeek.getDayOfWeekAsString());
+ assertFalse(wildcardDayOfWeek.hasWildcardMinute());
+ assertFalse(wildcardDayOfWeek.hasWildcardHour());
+ assertFalse(wildcardDayOfWeek.hasWildcardDayOfMonth());
+ assertFalse(wildcardDayOfWeek.hasWildcardMonth());
+ assertTrue(wildcardDayOfWeek.hasWildcardDayOfWeek());
+ }
+
+ @Test
+ public void testEqualsIsCanonical() {
+ String rawEntry = "* * */3 * *";
+ CrontabEntry input = CrontabEntry.parse(rawEntry);
+ assertNotEquals(
+ rawEntry + " is not the canonical form of " + input,
+ rawEntry,
+ input.toString());
+ assertEquals(
+ "The form returned by toString is canonical",
+ input.toString(),
+ CrontabEntry.parse(input.toString()).toString());
+ }
+
+ @Test
+ public void testBadEntries() {
+ List<String> badPatterns = ImmutableList.of(
+ "* * * * MON-SUN",
+ "* * **",
+ "0-59 0-59 * * *",
+ "1/1 * * * *",
+ "5 5 * MAR-JAN *",
+ "*/0 * * * *",
+ "0-59/0 * * * *",
+ "0-59/60 * * * *",
+ "* * * *, *",
+ "* * 1 * 1"
+ );
+
+ for (String pattern : badPatterns) {
+ assertNull(CrontabEntry.tryParse(pattern).orNull());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java b/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java
deleted file mode 100644
index 0082932..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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;
-
-import javax.annotation.Nullable;
-
-import com.google.common.base.Optional;
-
-import org.apache.aurora.gen.CronCollisionPolicy;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.configuration.ConfigurationManager;
-import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.apache.commons.lang.StringUtils;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Used by functions that expect field validation before being called.
- */
-public final class SanitizedCronJob {
- private final SanitizedConfiguration config;
- private final CrontabEntry crontabEntry;
-
- private SanitizedCronJob(IJobConfiguration unsanitized)
- throws CronException, ConfigurationManager.TaskDescriptionException {
-
- this(SanitizedConfiguration.fromUnsanitized(unsanitized));
- }
-
- private SanitizedCronJob(SanitizedConfiguration config) throws CronException {
- final IJobConfiguration job = config.getJobConfig();
- if (!hasCronSchedule(job)) {
- throw new CronException(String.format(
- "Not a valid cron job, %s has no cron schedule", JobKeys.canonicalString(job.getKey())));
- }
-
- Optional<CrontabEntry> entry = CrontabEntry.tryParse(job.getCronSchedule());
- if (!entry.isPresent()) {
- throw new CronException("Invalid cron schedule: " + job.getCronSchedule());
- }
-
- this.config = config;
- this.crontabEntry = entry.get();
- }
-
- /**
- * Get the default cron collision policy.
- *
- * @param policy A (possibly null) policy.
- * @return The given policy or a default if the policy was null.
- */
- public static CronCollisionPolicy orDefault(@Nullable CronCollisionPolicy policy) {
- return Optional.fromNullable(policy).or(CronCollisionPolicy.KILL_EXISTING);
- }
-
- /**
- * Create a SanitizedCronJob from a SanitizedConfiguration. SanitizedCronJob performs additional
- * validation to ensure that the provided job contains all properties needed to run it on a
- * cron schedule.
- *
- * @param config Config to validate.
- * @return Config wrapped in defaults.
- * @throws CronException If a cron-specific validation error occured.
- */
- public static SanitizedCronJob from(SanitizedConfiguration config)
- throws CronException {
-
- return new SanitizedCronJob(config);
- }
-
- /**
- * Create a cron job from an unsanitized input job. Suitable for RPC input validation.
- *
- * @param unsanitized Unsanitized input job.
- * @return A sanitized job if all validation succeeds.
- * @throws CronException If validation fails with a cron-specific error.
- * @throws ConfigurationManager.TaskDescriptionException If validation fails with a non
- * cron-specific error.
- */
- public static SanitizedCronJob fromUnsanitized(IJobConfiguration unsanitized)
- throws CronException, ConfigurationManager.TaskDescriptionException {
-
- return new SanitizedCronJob(unsanitized);
- }
-
- /**
- * Get this job's cron collision policy.
- *
- * @return This job's cron collision policy.
- */
- public CronCollisionPolicy getCronCollisionPolicy() {
- return orDefault(config.getJobConfig().getCronCollisionPolicy());
- }
-
- private static boolean hasCronSchedule(IJobConfiguration job) {
- checkNotNull(job);
- return !StringUtils.isEmpty(job.getCronSchedule());
- }
-
- /**
- * Returns the cron schedule associated with this job.
- *
- * @return The cron schedule associated with this job.
- */
- public CrontabEntry getCrontabEntry() {
- return crontabEntry;
- }
-
- /**
- * Returns the sanitized job configuration associated with the cron job.
- *
- * @return This cron job's sanitized job configuration.
- */
- public SanitizedConfiguration getSanitizedConfig() {
- return config;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java
new file mode 100644
index 0000000..e0935f5
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2013 Apache Software Foundation
+ *
+ * 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.noop;
+
+import javax.inject.Singleton;
+
+import com.google.inject.AbstractModule;
+
+import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CronScheduler;
+
+/**
+ * A Module to wire up a cron scheduler that does not actually schedule cron jobs.
+ *
+ * This class exists as a short term hack to get around a license compatibility issue - Real
+ * Implementation (TM) coming soon.
+ */
+public class NoopCronModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(CronScheduler.class).to(NoopCronScheduler.class);
+ bind(NoopCronScheduler.class).in(Singleton.class);
+
+ bind(CronPredictor.class).to(NoopCronPredictor.class);
+ bind(NoopCronPredictor.class).in(Singleton.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java
new file mode 100644
index 0000000..7b25152
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2013 Apache Software Foundation
+ *
+ * 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.noop;
+
+import java.util.Date;
+
+import org.apache.aurora.scheduler.cron.CronPredictor;
+
+/**
+ * A cron predictor that always suggests that the next run is Unix epoch time.
+ *
+ * This class exists as a short term hack to get around a license compatibility issue - Real
+ * Implementation (TM) coming soon.
+ */
+class NoopCronPredictor implements CronPredictor {
+ @Override
+ public Date predictNextRun(String schedule) {
+ return new Date(0);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java
new file mode 100644
index 0000000..a31551c
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright 2013 Apache Software Foundation
+ *
+ * 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.noop;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.AbstractIdleService;
+
+import org.apache.aurora.scheduler.cron.CronScheduler;
+
+/**
+ * A cron scheduler that accepts cron jobs but never runs them. Useful if you want to hook up an
+ * external triggering mechanism (e.g. a system cron job that calls the startCronJob RPC manually
+ * on an interval).
+ *
+ * This class exists as a short term hack to get around a license compatibility issue - Real
+ * Implementation (TM) coming soon.
+ */
+class NoopCronScheduler extends AbstractIdleService implements CronScheduler {
+ private static final Logger LOG = Logger.getLogger(NoopCronScheduler.class.getName());
+
+ // Keep a list of schedules we've seen.
+ private final Set<String> schedules = Collections.synchronizedSet(Sets.<String>newHashSet());
+
+ @Override
+ public void startUp() throws Exception {
+ LOG.warning("NO-OP cron scheduler is in use. Cron jobs submitted will not be triggered!");
+ }
+
+ @Override
+ public void shutDown() {
+ // No-op.
+ }
+
+ @Override
+ public String schedule(String schedule, Runnable task) {
+ schedules.add(schedule);
+
+ LOG.warning(String.format(
+ "NO-OP cron scheduler is in use! %s with schedule %s WILL NOT be automatically triggered!",
+ task,
+ schedule));
+
+ return schedule;
+ }
+
+ @Override
+ public void deschedule(String key) throws IllegalStateException {
+ schedules.remove(key);
+ }
+
+ @Override
+ public Optional<String> getSchedule(String key) throws IllegalStateException {
+ return schedules.contains(key)
+ ? Optional.of(key)
+ : Optional.<String>absent();
+ }
+
+ @Override
+ public boolean isValidSchedule(@Nullable String schedule) {
+ // Accept everything.
+ return schedule != null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java
deleted file mode 100644
index fc02264..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.Date;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.inject.Inject;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableMap;
-
-import com.twitter.common.base.Supplier;
-import com.twitter.common.stats.Stats;
-import com.twitter.common.util.BackoffHelper;
-
-import org.apache.aurora.gen.CronCollisionPolicy;
-import org.apache.aurora.gen.ScheduleStatus;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.base.Query;
-import org.apache.aurora.scheduler.base.Tasks;
-import org.apache.aurora.scheduler.configuration.ConfigurationManager;
-import org.apache.aurora.scheduler.cron.CronException;
-import org.apache.aurora.scheduler.cron.CronJobManager;
-import org.apache.aurora.scheduler.cron.SanitizedCronJob;
-import org.apache.aurora.scheduler.state.StateManager;
-import org.apache.aurora.scheduler.storage.Storage;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
-
-import org.quartz.DisallowConcurrentExecution;
-import org.quartz.Job;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import static org.apache.aurora.gen.ScheduleStatus.KILLING;
-
-/**
- * Encapsulates the logic behind a single trigger of a single job key. Multiple executions may run
- * concurrently but only a single instance will be active at a time per job key.
- *
- * <p>
- * Executions may block for long periods of time when waiting for a kill to complete. The Quartz
- * scheduler should therefore be configured with a large number of threads.
- */
-@DisallowConcurrentExecution
-class AuroraCronJob implements Job {
- private static final Logger LOG = Logger.getLogger(AuroraCronJob.class.getName());
-
- private static final AtomicLong CRON_JOB_TRIGGERS = Stats.exportLong("cron_job_triggers");
- private static final AtomicLong CRON_JOB_MISFIRES = Stats.exportLong("cron_job_misfires");
- private static final AtomicLong CRON_JOB_PARSE_FAILURES =
- Stats.exportLong("cron_job_parse_failures");
- private static final AtomicLong CRON_JOB_COLLISIONS = Stats.exportLong("cron_job_collisions");
-
- @VisibleForTesting
- static final Optional<String> KILL_AUDIT_MESSAGE = Optional.of("Killed by cronScheduler");
-
- private final Storage storage;
- private final StateManager stateManager;
- private final CronJobManager cronJobManager;
- private final BackoffHelper delayedStartBackoff;
-
- @Inject
- AuroraCronJob(
- Config config,
- Storage storage,
- StateManager stateManager,
- CronJobManager cronJobManager) {
-
- this.storage = checkNotNull(storage);
- this.stateManager = checkNotNull(stateManager);
- this.cronJobManager = checkNotNull(cronJobManager);
- this.delayedStartBackoff = checkNotNull(config.getDelayedStartBackoff());
- }
-
- private static final class DeferredLaunch {
- private final Map<Integer, ITaskConfig> pendingTasks;
- private final Set<String> activeTaskIds;
-
- private DeferredLaunch(Map<Integer, ITaskConfig> pendingTasks, Set<String> activeTaskIds) {
- this.pendingTasks = pendingTasks;
- this.activeTaskIds = activeTaskIds;
- }
- }
-
- @Override
- public void execute(JobExecutionContext context) throws JobExecutionException {
- // We assume quartz prevents concurrent runs of this job for a given job key. This allows us
- // to avoid races where we might kill another run's tasks.
- checkState(context.getJobDetail().isConcurrentExectionDisallowed());
-
- doExecute(Quartz.auroraJobKey(context.getJobDetail().getKey()));
- }
-
- @VisibleForTesting
- void doExecute(final IJobKey key) throws JobExecutionException {
- final String path = JobKeys.canonicalString(key);
-
- final Optional<DeferredLaunch> deferredLaunch = storage.write(
- new Storage.MutateWork.Quiet<Optional<DeferredLaunch>>() {
- @Override
- public Optional<DeferredLaunch> apply(Storage.MutableStoreProvider storeProvider) {
- Optional<IJobConfiguration> config =
- storeProvider.getJobStore().fetchJob(cronJobManager.getManagerKey(), key);
- if (!config.isPresent()) {
- LOG.warning(String.format(
- "Cron was triggered for %s but no job with that key was found in storage.",
- path));
- CRON_JOB_MISFIRES.incrementAndGet();
- return Optional.absent();
- }
-
- SanitizedCronJob cronJob;
- try {
- cronJob = SanitizedCronJob.fromUnsanitized(config.get());
- } catch (ConfigurationManager.TaskDescriptionException | CronException e) {
- LOG.warning(String.format(
- "Invalid cron job for %s in storage - failed to parse with %s", key, e));
- CRON_JOB_PARSE_FAILURES.incrementAndGet();
- return Optional.absent();
- }
-
- CronCollisionPolicy collisionPolicy = cronJob.getCronCollisionPolicy();
- LOG.info(String.format(
- "Cron triggered for %s at %s with policy %s", path, new Date(), collisionPolicy));
- CRON_JOB_TRIGGERS.incrementAndGet();
-
- ImmutableMap<Integer, ITaskConfig> pendingTasks =
- ImmutableMap.copyOf(cronJob.getSanitizedConfig().getTaskConfigs());
-
- final Query.Builder activeQuery = Query.jobScoped(key).active();
- Set<String> activeTasks =
- Tasks.ids(storeProvider.getTaskStore().fetchTasks(activeQuery));
-
- if (activeTasks.isEmpty()) {
- stateManager.insertPendingTasks(pendingTasks);
- return Optional.absent();
- }
-
- CRON_JOB_COLLISIONS.incrementAndGet();
- switch (collisionPolicy) {
- case KILL_EXISTING:
- return Optional.of(new DeferredLaunch(pendingTasks, activeTasks));
-
- case RUN_OVERLAP:
- LOG.severe(String.format("Ignoring trigger for job %s with deprecated collision"
- + "policy RUN_OVERLAP due to unterminated active tasks.", path));
- return Optional.absent();
-
- case CANCEL_NEW:
- return Optional.absent();
-
- default:
- LOG.severe("Unrecognized cron collision policy: " + collisionPolicy);
- return Optional.absent();
- }
- }
- }
- );
-
- if (!deferredLaunch.isPresent()) {
- return;
- }
-
- for (String taskId : deferredLaunch.get().activeTaskIds) {
- stateManager.changeState(
- taskId,
- Optional.<ScheduleStatus>absent(),
- KILLING,
- KILL_AUDIT_MESSAGE);
- }
- LOG.info(String.format("Waiting for job to terminate before launching cron job %s.", path));
-
- final Query.Builder query = Query.taskScoped(deferredLaunch.get().activeTaskIds).active();
- try {
- // NOTE: We block the quartz execution thread here until we've successfully killed our
- // ancestor. We mitigate this by using a cached thread pool for quartz.
- delayedStartBackoff.doUntilSuccess(new Supplier<Boolean>() {
- @Override
- public Boolean get() {
- if (Storage.Util.consistentFetchTasks(storage, query).isEmpty()) {
- LOG.info("Initiating delayed launch of cron " + path);
- stateManager.insertPendingTasks(deferredLaunch.get().pendingTasks);
- return true;
- } else {
- LOG.info("Not yet safe to run cron " + path);
- return false;
- }
- }
- });
- } catch (InterruptedException e) {
- LOG.log(Level.WARNING, "Interrupted while trying to launch cron " + path, e);
- Thread.currentThread().interrupt();
- throw new JobExecutionException(e);
- }
- }
-
- static class Config {
- private final BackoffHelper delayedStartBackoff;
-
- Config(BackoffHelper delayedStartBackoff) {
- this.delayedStartBackoff = checkNotNull(delayedStartBackoff);
- }
-
- public BackoffHelper getDelayedStartBackoff() {
- return delayedStartBackoff;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java
deleted file mode 100644
index c5268cb..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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 javax.inject.Inject;
-import javax.inject.Provider;
-
-import org.quartz.Job;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-import org.quartz.spi.JobFactory;
-import org.quartz.spi.TriggerFiredBundle;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-/**
-* Adapter that allows AuroraCronJobs to be constructed by Guice instead of directly by quartz.
-*/
-class AuroraCronJobFactory implements JobFactory {
- private final Provider<AuroraCronJob> auroraCronJobProvider;
-
- @Inject
- AuroraCronJobFactory(Provider<AuroraCronJob> auroraCronJobProvider) {
- this.auroraCronJobProvider = checkNotNull(auroraCronJobProvider);
- }
-
- @Override
- public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
- checkState(AuroraCronJob.class.equals(bundle.getJobDetail().getJobClass()),
- "Quartz tried to run a type of job we don't know about: "
- + bundle.getJobDetail().getJobClass());
-
- return auroraCronJobProvider.get();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java
deleted file mode 100644
index 8a5f569..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.Map;
-import java.util.TimeZone;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableMap;
-
-import org.apache.aurora.gen.CronCollisionPolicy;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.cron.CronException;
-import org.apache.aurora.scheduler.cron.CronJobManager;
-import org.apache.aurora.scheduler.cron.CrontabEntry;
-import org.apache.aurora.scheduler.cron.SanitizedCronJob;
-import org.apache.aurora.scheduler.storage.JobStore;
-import org.apache.aurora.scheduler.storage.Storage;
-import org.apache.aurora.scheduler.storage.Storage.MutateWork;
-import org.apache.aurora.scheduler.storage.Storage.Work;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.quartz.JobDetail;
-import org.quartz.JobKey;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-import org.quartz.impl.matchers.GroupMatcher;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * NOTE: The source of truth for whether a cron job exists or not is always the JobStore. If state
- * somehow becomes inconsistent (i.e. a job key is scheduled for execution but its underlying
- * JobConfiguration does not exist in storage the execution of the job will log a warning and
- * exit).
- */
-class CronJobManagerImpl implements CronJobManager {
- private static final Logger LOG = Logger.getLogger(CronJobManagerImpl.class.getName());
-
- private final Storage storage;
- private final Scheduler scheduler;
- private final TimeZone timeZone;
-
- @Inject
- CronJobManagerImpl(Storage storage, Scheduler scheduler, TimeZone timeZone) {
- this.storage = checkNotNull(storage);
- this.scheduler = checkNotNull(scheduler);
- this.timeZone = checkNotNull(timeZone);
- }
-
- @Override
- public String getManagerKey() {
- return "CRON";
- }
-
- @Override
- public void startJobNow(final IJobKey jobKey) throws CronException {
- checkNotNull(jobKey);
-
- storage.weaklyConsistentRead(new Work<Void, CronException>() {
- @Override
- public Void apply(Storage.StoreProvider storeProvider) throws CronException {
- checkCronExists(jobKey, storeProvider.getJobStore());
- triggerJob(jobKey);
- return null;
- }
- });
- }
-
- private void triggerJob(IJobKey jobKey) throws CronException {
- try {
- scheduler.triggerJob(Quartz.jobKey(jobKey));
- } catch (SchedulerException e) {
- throw new CronException(e);
- }
- LOG.info(String.format("Triggered cron job for %s.", JobKeys.canonicalString(jobKey)));
- }
-
- private static void checkNoRunOverlap(SanitizedCronJob cronJob) throws CronException {
- // NOTE: We check at create and update instead of in SanitizedCronJob to allow existing jobs
- // but reject new ones.
- if (CronCollisionPolicy.RUN_OVERLAP.equals(cronJob.getCronCollisionPolicy())) {
- throw new CronException(
- "The RUN_OVERLAP collision policy has been removed (AURORA-38).");
- }
- }
-
- @Override
- public void updateJob(final SanitizedCronJob config) throws CronException {
- checkNotNull(config);
- checkNoRunOverlap(config);
-
- final IJobKey jobKey = config.getSanitizedConfig().getJobConfig().getKey();
- storage.write(new MutateWork.NoResult<CronException>() {
- @Override
- public void execute(Storage.MutableStoreProvider storeProvider) throws CronException {
- checkCronExists(jobKey, storeProvider.getJobStore());
-
- removeJob(jobKey, storeProvider.getJobStore());
- descheduleJob(jobKey);
- saveJob(config, storeProvider.getJobStore());
- scheduleJob(config.getCrontabEntry(), jobKey);
- }
- });
- }
-
- @Override
- public void createJob(final SanitizedCronJob cronJob) throws CronException {
- checkNotNull(cronJob);
- checkNoRunOverlap(cronJob);
-
- final IJobKey jobKey = cronJob.getSanitizedConfig().getJobConfig().getKey();
- storage.write(new MutateWork.NoResult<CronException>() {
- @Override
- protected void execute(Storage.MutableStoreProvider storeProvider) throws CronException {
- checkNotExists(jobKey, storeProvider.getJobStore());
-
- saveJob(cronJob, storeProvider.getJobStore());
- scheduleJob(cronJob.getCrontabEntry(), jobKey);
- }
- });
- }
-
- private void checkNotExists(IJobKey jobKey, JobStore jobStore) throws CronException {
- if (jobStore.fetchJob(getManagerKey(), jobKey).isPresent()) {
- throw new CronException(
- String.format("Job already exists for %s.", JobKeys.canonicalString(jobKey)));
- }
- }
-
- private void checkCronExists(IJobKey jobKey, JobStore jobStore) throws CronException {
- if (!jobStore.fetchJob(getManagerKey(), jobKey).isPresent()) {
- throw new CronException(
- String.format("No cron template found for %s.", JobKeys.canonicalString(jobKey)));
- }
- }
-
- private void removeJob(IJobKey jobKey, JobStore.Mutable jobStore) {
- jobStore.removeJob(jobKey);
- LOG.info(
- String.format("Deleted cron job %s from storage.", JobKeys.canonicalString(jobKey)));
- }
-
- private void saveJob(SanitizedCronJob cronJob, JobStore.Mutable jobStore) {
- IJobConfiguration config = cronJob.getSanitizedConfig().getJobConfig();
-
- jobStore.saveAcceptedJob(getManagerKey(), config);
- LOG.info(String.format(
- "Saved new cron job %s to storage.", JobKeys.canonicalString(config.getKey())));
- }
-
- // TODO(ksweeney): Consider exposing this in the interface and making caller responsible.
- void scheduleJob(CrontabEntry crontabEntry, IJobKey jobKey) throws CronException {
- try {
- scheduler.scheduleJob(
- Quartz.jobDetail(jobKey, AuroraCronJob.class),
- Quartz.cronTrigger(crontabEntry, timeZone));
- } catch (SchedulerException e) {
- throw new CronException(e);
- }
- LOG.info(String.format(
- "Scheduled job %s with schedule %s.", JobKeys.canonicalString(jobKey), crontabEntry));
- }
-
- @Override
- public Iterable<IJobConfiguration> getJobs() {
- // NOTE: no synchronization is needed here since we don't touch internal quartz state.
- return storage.consistentRead(new Work.Quiet<Iterable<IJobConfiguration>>() {
- @Override
- public Iterable<IJobConfiguration> apply(Storage.StoreProvider storeProvider) {
- return storeProvider.getJobStore().fetchJobs(getManagerKey());
- }
- });
- }
-
- @Override
- public boolean hasJob(final IJobKey jobKey) {
- checkNotNull(jobKey);
-
- return storage.consistentRead(new Work.Quiet<Boolean>() {
- @Override
- public Boolean apply(Storage.StoreProvider storeProvider) {
- return storeProvider.getJobStore().fetchJob(getManagerKey(), jobKey).isPresent();
- }
- });
- }
-
- @Override
- public boolean deleteJob(final IJobKey jobKey) {
- checkNotNull(jobKey);
-
- return storage.write(new MutateWork.Quiet<Boolean>() {
- @Override
- public Boolean apply(Storage.MutableStoreProvider storeProvider) {
- if (!hasJob(jobKey)) {
- return false;
- }
-
- removeJob(jobKey, storeProvider.getJobStore());
- descheduleJob(jobKey);
- return true;
- }
- });
- }
-
- private void descheduleJob(IJobKey jobKey) {
- String path = JobKeys.canonicalString(jobKey);
- try {
- // TODO(ksweeney): Consider interrupting the running job here.
- // There's a race here where an old running job could fail to find the old config. That's
- // fine given that the behavior of AuroraCronJob is to log an error and exit if it's unable
- // to find a job for its key.
- scheduler.deleteJob(Quartz.jobKey(jobKey));
- LOG.info("Successfully descheduled " + path + ".");
- } catch (SchedulerException e) {
- LOG.log(Level.WARNING, "Error when attempting to deschedule " + path + ": " + e, e);
- }
- }
-
- @Override
- public Map<IJobKey, CrontabEntry> getScheduledJobs() {
- // NOTE: no synchronization is needed here since this is just a dump of internal quartz state
- // for debugging.
- ImmutableMap.Builder<IJobKey, CrontabEntry> scheduledJobs = ImmutableMap.builder();
- try {
- for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.<JobKey>anyGroup())) {
- Optional<JobDetail> jobDetail = Optional.fromNullable(scheduler.getJobDetail(jobKey));
- if (jobDetail.isPresent()) {
- scheduledJobs.put(
- Quartz.auroraJobKey(jobKey), CrontabEntry.parse(jobDetail.get().getDescription()));
- }
- }
- } catch (SchedulerException e) {
- throw Throwables.propagate(e);
- }
- return scheduledJobs.build();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java
deleted file mode 100644
index 64fa068..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import com.google.common.eventbus.Subscribe;
-import com.google.common.util.concurrent.AbstractIdleService;
-import com.twitter.common.application.ShutdownRegistry;
-import com.twitter.common.base.Command;
-import com.twitter.common.stats.Stats;
-
-import org.apache.aurora.scheduler.configuration.ConfigurationManager;
-import org.apache.aurora.scheduler.cron.CronException;
-import org.apache.aurora.scheduler.cron.SanitizedCronJob;
-import org.apache.aurora.scheduler.events.PubsubEvent;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.quartz.Scheduler;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Manager for startup and teardown of Quartz scheduler.
- */
-class CronLifecycle extends AbstractIdleService implements PubsubEvent.EventSubscriber {
- private static final Logger LOG = Logger.getLogger(CronLifecycle.class.getName());
-
- private static final AtomicInteger RUNNING_FLAG = Stats.exportInt("quartz_scheduler_running");
- private static final AtomicInteger LOADED_FLAG = Stats.exportInt("cron_jobs_loaded");
- private static final AtomicLong LAUNCH_FAILURES = Stats.exportLong("cron_job_launch_failures");
-
- private final Scheduler scheduler;
- private final ShutdownRegistry shutdownRegistry;
- private final CronJobManagerImpl cronJobManager;
-
- @Inject
- CronLifecycle(
- Scheduler scheduler,
- ShutdownRegistry shutdownRegistry,
- CronJobManagerImpl cronJobManager) {
-
- this.scheduler = checkNotNull(scheduler);
- this.shutdownRegistry = checkNotNull(shutdownRegistry);
- this.cronJobManager = checkNotNull(cronJobManager);
- }
-
- /**
- * Notifies the cronScheduler job manager that the scheduler is active, and job configurations
- * are ready to load.
- *
- * @param schedulerActive Event.
- */
- @Subscribe
- public void schedulerActive(PubsubEvent.SchedulerActive schedulerActive) {
- startAsync();
- shutdownRegistry.addAction(new Command() {
- @Override
- public void execute() {
- CronLifecycle.this.stopAsync().awaitTerminated();
- }
- });
- awaitRunning();
- }
-
- @Override
- protected void startUp() throws Exception {
- LOG.info("Starting Quartz cron scheduler" + scheduler.getSchedulerName() + ".");
- scheduler.start();
- RUNNING_FLAG.set(1);
-
- // TODO(ksweeney): Refactor the interface - we really only need the job keys here.
- for (IJobConfiguration job : cronJobManager.getJobs()) {
- try {
- SanitizedCronJob cronJob = SanitizedCronJob.fromUnsanitized(job);
- cronJobManager.scheduleJob(
- cronJob.getCrontabEntry(),
- cronJob.getSanitizedConfig().getJobConfig().getKey());
- } catch (CronException | ConfigurationManager.TaskDescriptionException e) {
- logLaunchFailure(job, e);
- }
- }
- LOADED_FLAG.set(1);
- }
-
- private void logLaunchFailure(IJobConfiguration job, Exception e) {
- LAUNCH_FAILURES.incrementAndGet();
- LOG.log(Level.SEVERE, "Scheduling failed for recovered job " + job, e);
- }
-
- @Override
- protected void shutDown() throws Exception {
- LOG.info("Shutting down Quartz cron scheduler.");
- scheduler.shutdown();
- RUNNING_FLAG.set(0);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
deleted file mode 100644
index 6934828..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.TimeZone;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.logging.Logger;
-
-import javax.inject.Singleton;
-
-import com.google.common.base.Throwables;
-import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
-import com.twitter.common.args.Arg;
-import com.twitter.common.args.CmdLine;
-import com.twitter.common.quantity.Amount;
-import com.twitter.common.quantity.Time;
-import com.twitter.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.events.PubsubEventModule;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-import org.quartz.impl.DirectSchedulerFactory;
-import org.quartz.simpl.RAMJobStore;
-import org.quartz.simpl.SimpleThreadPool;
-
-/**
- * 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 = Logger.getLogger(CronModule.class.getName());
-
- @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(100);
-
- @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(1L, 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));
-
- // 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())));
-
- bind(CronLifecycle.class).in(Singleton.class);
- PubsubEventModule.bindSubscriber(binder(), CronLifecycle.class);
- }
-
- @Provides
- private TimeZone provideTimeZone() {
- TimeZone timeZone = TimeZone.getTimeZone(CRON_TIMEZONE.get());
- TimeZone systemTimeZone = TimeZone.getDefault();
- if (!timeZone.equals(systemTimeZone)) {
- LOG.warning("Cron schedules are configured to fire according to timezone "
- + timeZone.getDisplayName()
- + " but system timezone is set to "
- + systemTimeZone.getDisplayName());
- }
- return timeZone;
- }
-
- /*
- * NOTE: Quartz implements DirectSchedulerFactory as a mutable global singleton in a static
- * variable. While the Scheduler instances it produces are independent we synchronize here to
- * avoid an initialization race across injectors. In practice this only shows up during testing;
- * production Aurora instances will only have one object graph at a time.
- */
- @Provides
- @Singleton
- private static synchronized Scheduler provideScheduler(AuroraCronJobFactory jobFactory) {
- SimpleThreadPool threadPool = new SimpleThreadPool(NUM_THREADS.get(), Thread.NORM_PRIORITY);
- threadPool.setMakeThreadsDaemons(true);
-
- DirectSchedulerFactory schedulerFactory = DirectSchedulerFactory.getInstance();
- String schedulerName = "aurora-cron-" + ID_GENERATOR.incrementAndGet();
- try {
- schedulerFactory.createScheduler(schedulerName, schedulerName, threadPool, new RAMJobStore());
- Scheduler scheduler = schedulerFactory.getScheduler(schedulerName);
- scheduler.setJobFactory(jobFactory);
- return scheduler;
- } catch (SchedulerException e) {
- LOG.severe("Error initializing Quartz cron scheduler: " + e);
- throw Throwables.propagate(e);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java
deleted file mode 100644
index fb24c28..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.Date;
-import java.util.TimeZone;
-
-import javax.inject.Inject;
-
-import com.twitter.common.util.Clock;
-
-import org.apache.aurora.scheduler.cron.CronPredictor;
-import org.apache.aurora.scheduler.cron.CrontabEntry;
-import org.quartz.CronExpression;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-class CronPredictorImpl implements CronPredictor {
- private final Clock clock;
- private final TimeZone timeZone;
-
- @Inject
- CronPredictorImpl(Clock clock, TimeZone timeZone) {
- this.clock = checkNotNull(clock);
- this.timeZone = checkNotNull(timeZone);
- }
-
- @Override
- public Date predictNextRun(CrontabEntry schedule) {
- CronExpression cronExpression = Quartz.cronExpression(schedule, timeZone);
- return cronExpression.getNextValidTimeAfter(new Date(clock.nowMillis()));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java
deleted file mode 100644
index 3bef22d..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.logging.Level;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Iterables;
-import com.twitter.common.base.Function;
-
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.cron.CronScheduler;
-import org.apache.aurora.scheduler.cron.CrontabEntry;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.quartz.CronTrigger;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import static org.apache.aurora.scheduler.cron.quartz.Quartz.jobKey;
-
-class CronSchedulerImpl implements CronScheduler {
- private static final Logger LOG = Logger.getLogger(CronSchedulerImpl.class.getName());
-
- private final Scheduler scheduler;
-
- @Inject
- CronSchedulerImpl(Scheduler scheduler) {
- this.scheduler = checkNotNull(scheduler);
- }
-
- @Override
- public Optional<CrontabEntry> getSchedule(IJobKey jobKey) throws IllegalStateException {
- checkNotNull(jobKey);
-
- try {
- return Optional.of(Iterables.getOnlyElement(
- FluentIterable.from(scheduler.getTriggersOfJob(jobKey(jobKey)))
- .filter(CronTrigger.class)
- .transform(new Function<CronTrigger, CrontabEntry>() {
- @Override
- public CrontabEntry apply(CronTrigger trigger) {
- return Quartz.crontabEntry(trigger);
- }
- })));
- } catch (SchedulerException e) {
- LOG.log(Level.SEVERE,
- "Error reading job " + JobKeys.canonicalString(jobKey) + " cronExpression Quartz: " + e,
- e);
- return Optional.absent();
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java
deleted file mode 100644
index 63aaade..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * 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.text.ParseException;
-import java.util.List;
-import java.util.TimeZone;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ContiguousSet;
-import com.google.common.collect.DiscreteDomain;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Range;
-
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.cron.CrontabEntry;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.quartz.CronExpression;
-import org.quartz.CronScheduleBuilder;
-import org.quartz.CronTrigger;
-import org.quartz.Job;
-import org.quartz.JobBuilder;
-import org.quartz.JobDetail;
-import org.quartz.JobKey;
-import org.quartz.TriggerBuilder;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Utilities for converting Aurora datatypes to Quartz datatypes.
- */
-final class Quartz {
- private Quartz() {
- // Utility class.
- }
-
- /**
- * Convert an Aurora CrontabEntry to a Quartz CronExpression.
- */
- static CronExpression cronExpression(CrontabEntry entry, TimeZone timeZone) {
- String dayOfMonth;
- if (entry.hasWildcardDayOfMonth()) {
- dayOfMonth = "?"; // special quartz token meaning "don't care"
- } else {
- dayOfMonth = entry.getDayOfMonthAsString();
- }
- String dayOfWeek;
- if (entry.hasWildcardDayOfWeek() && !entry.hasWildcardDayOfMonth()) {
- dayOfWeek = "?";
- } else {
- List<Integer> daysOfWeek = Lists.newArrayList();
- for (Range<Integer> range : entry.getDayOfWeek().asRanges()) {
- for (int i : ContiguousSet.create(range, DiscreteDomain.integers())) {
- daysOfWeek.add(i + 1); // Quartz has an off-by-one with what the "standard" defines.
- }
- }
- dayOfWeek = Joiner.on(",").join(daysOfWeek);
- }
-
- String rawCronExpresion = Joiner.on(" ").join(
- "0",
- entry.getMinuteAsString(),
- entry.getHourAsString(),
- dayOfMonth,
- entry.getMonthAsString(),
- dayOfWeek);
- CronExpression cronExpression;
- try {
- cronExpression = new CronExpression(rawCronExpresion);
- } catch (ParseException e) {
- throw Throwables.propagate(e);
- }
- cronExpression.setTimeZone(timeZone);
- return cronExpression;
- }
-
- /**
- * Convert a Quartz JobKey to an Aurora IJobKey.
- */
- static IJobKey auroraJobKey(org.quartz.JobKey jobKey) {
- return JobKeys.parse(jobKey.getName());
- }
-
- /**
- * Convert an Aurora IJobKey to a Quartz JobKey.
- */
- static JobKey jobKey(IJobKey jobKey) {
- return JobKey.jobKey(JobKeys.canonicalString(jobKey));
- }
-
- static CronTrigger cronTrigger(CrontabEntry schedule, TimeZone timeZone) {
- return TriggerBuilder.newTrigger()
- .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression(schedule, timeZone)))
- .withDescription(schedule.toString())
- .build();
- }
-
- static JobDetail jobDetail(IJobKey jobKey, Class<? extends Job> jobClass) {
- checkNotNull(jobKey);
- checkNotNull(jobClass);
-
- return JobBuilder.newJob(jobClass)
- .withIdentity(jobKey(jobKey))
- .build();
- }
-
- static CrontabEntry crontabEntry(CronTrigger cronTrigger) {
- return CrontabEntry.parse(cronTrigger.getDescription());
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java b/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java
new file mode 100644
index 0000000..61b01d2
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2013 Apache Software Foundation
+ *
+ * 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.testing;
+
+import java.io.InputStreamReader;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.twitter.common.util.Clock;
+import com.twitter.common.util.testing.FakeClock;
+
+import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CronScheduler;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Abstract test to verify conformance with the {@link CronScheduler} interface.
+ */
+public abstract class AbstractCronIT {
+ private static final String WILDCARD_SCHEDULE = "* * * * *";
+
+ /**
+ * Child should return an instance of the {@link CronScheduler} test under test here.
+ */
+ protected abstract CronScheduler makeCronScheduler() throws Exception;
+
+ /**
+ * Child should return an instance of the {@link CronPredictor} under test here.
+ *
+ * @param clock The clock the predictor should use.
+ */
+ protected abstract CronPredictor makeCronPredictor(Clock clock) throws Exception;
+
+ @Test
+ public void testCronSchedulerLifecycle() throws Exception {
+ CronScheduler scheduler = makeCronScheduler();
+
+ scheduler.startAsync().awaitRunning();
+ final CountDownLatch cronRan = new CountDownLatch(1);
+ scheduler.schedule(WILDCARD_SCHEDULE, new Runnable() {
+ @Override public void run() {
+ cronRan.countDown();
+ }
+ });
+ cronRan.await();
+ scheduler.stopAsync().awaitTerminated();
+ }
+
+ @Test
+ public void testCronPredictorConforms() throws Exception {
+ FakeClock clock = new FakeClock();
+ CronPredictor cronPredictor = makeCronPredictor(clock);
+
+ for (TriggerPrediction triggerPrediction : getExpectedTriggerPredictions()) {
+ List<Long> results = Lists.newArrayList();
+ clock.setNowMillis(0);
+ for (int i = 0; i < triggerPrediction.getTriggerTimes().size(); i++) {
+ Date nextTriggerTime = cronPredictor.predictNextRun(triggerPrediction.getSchedule());
+ results.add(nextTriggerTime.getTime());
+ clock.setNowMillis(nextTriggerTime.getTime());
+ }
+ assertEquals("Cron schedule "
+ + triggerPrediction.getSchedule()
+ + " should have have predicted trigger times "
+ + triggerPrediction.getTriggerTimes()
+ + " but predicted "
+ + results
+ + " instead.", triggerPrediction.getTriggerTimes(), results);
+ }
+ }
+
+ @Test
+ public void testCronScheduleValidatorAcceptsValidSchedules() throws Exception {
+ CronScheduler cron = makeCronScheduler();
+
+ for (TriggerPrediction triggerPrediction : getExpectedTriggerPredictions()) {
+ assertTrue("Cron schedule " + triggerPrediction.getSchedule() + " should pass validation.",
+ cron.isValidSchedule(triggerPrediction.getSchedule()));
+ }
+ }
+
+ private static List<TriggerPrediction> getExpectedTriggerPredictions() {
+ return new Gson()
+ .fromJson(
+ new InputStreamReader(
+ AbstractCronIT.class.getResourceAsStream("cron-schedule-predictions.json")),
+ new TypeToken<List<TriggerPrediction>>() { }.getType());
+ }
+
+ /**
+ * A schedule and the expected iteratively-applied prediction results.
+ */
+ public static class TriggerPrediction {
+ private String schedule;
+ private List<Long> triggerTimes;
+
+ private TriggerPrediction() {
+ // GSON constructor.
+ }
+
+ public TriggerPrediction(String schedule, List<Long> triggerTimes) {
+ this.schedule = schedule;
+ this.triggerTimes = triggerTimes;
+ }
+
+ public String getSchedule() {
+ return schedule;
+ }
+
+ public List<Long> getTriggerTimes() {
+ return ImmutableList.copyOf(triggerTimes);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/5de2b1ab/src/main/java/org/apache/aurora/scheduler/http/Cron.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/Cron.java b/src/main/java/org/apache/aurora/scheduler/http/Cron.java
index d8c44f8..80a398a 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/Cron.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/Cron.java
@@ -26,7 +26,7 @@ import javax.ws.rs.core.Response;
import com.google.common.collect.ImmutableMap;
-import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.state.CronJobManager;
/**
* HTTP interface to dump state of the internal cron scheduler.
@@ -50,6 +50,7 @@ public class Cron {
public Response dumpContents() {
Map<String, Object> response = ImmutableMap.<String, Object>builder()
.put("scheduled", cronManager.getScheduledJobs())
+ .put("pending", cronManager.getPendingRuns())
.build();
return Response.ok(response).build();