pax_global_header00006660000000000000000000000064130354775750014532gustar00rootroot0000000000000052 comment=2a882d1ec48869436b016f6700654a13a60d27fe trapperkeeper-scheduler-0.1.0/000077500000000000000000000000001303547757500163555ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/.gitignore000066400000000000000000000002121303547757500203400ustar00rootroot00000000000000pom.xml pom.xml.asc *jar /lib/ /classes/ /target/ /checkouts/ .lein-deps-sum .lein-repl-history .lein-plugins/ .lein-failures .nrepl-port trapperkeeper-scheduler-0.1.0/.travis.yml000066400000000000000000000007731303547757500204750ustar00rootroot00000000000000language: clojure lein: lein2 jdk: - oraclejdk7 - openjdk7 script: ./ext/travisci/test.sh notifications: email: false hipchat: rooms: secure: CfGS3yYsocLruSP0lRr9HFwzdZ1HFw4rFEVdJkP/i0i8aIPY3XB3vTQNxw/lIBVRDwWHaUfA+xnyK3itCxt0M0UtPDp1BkU6abLr8Apjet/nJJ2icBvQfnpx1hb6xBpoE69vpRiIVewoU69UPFjXdZd2D1BWX58tNbdV9CA8Ezw= template: - ! '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}' - ! 'Change view: %{compare_url}' - ! 'Build details: %{build_url}' trapperkeeper-scheduler-0.1.0/CHANGELOG.md000066400000000000000000000005031303547757500201640ustar00rootroot00000000000000## 0.1.0 * Add the concept of group-id to the job creation endpoints to allow jobs to be grouped together for listing and cancellation. * Fix a potential memory leak with jobs created using `after` * Add an interface to return the total number of jobs and the number of jobs in a group-id ## 0.0.1 * Initial release trapperkeeper-scheduler-0.1.0/CONTRIBUTING.md000066400000000000000000000010241303547757500206030ustar00rootroot00000000000000# How to contribute Third-party patches are essential for keeping Puppet Labs open-source projects great. We want to keep it as easy as possible to contribute changes that allow you to get the most out of our projects. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. For more info, see our canonical guide to contributing: [https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md) trapperkeeper-scheduler-0.1.0/LICENSE000066400000000000000000000013271303547757500173650ustar00rootroot00000000000000 Trapperkeeper Scheduler - A TK service for scheduling background tasks Copyright (C) 2005-2015 Puppet Labs Inc Puppet Labs can be contacted at: info@puppetlabs.com 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. trapperkeeper-scheduler-0.1.0/README.md000066400000000000000000000114671303547757500176450ustar00rootroot00000000000000# Trapperkeeper Scheduler Service [![Build Status](https://travis-ci.org/puppetlabs/trapperkeeper-scheduler.svg)](https://travis-ci.org/puppetlabs/trapperkeeper-scheduler) [![Clojars Project](http://clojars.org/puppetlabs/trapperkeeper-scheduler/latest-version.svg)](http://clojars.org/puppetlabs/trapperkeeper-scheduler) A Trapperkeeper service that provides a simple API for scheduling background tasks. Other Trapperkeeper services may specify a dependency on the Scheduler service, and then use its functions to schedule and cancel jobs to be run on background worker threads. ### What Does This Service Do? The `SchedulerService` provides some simple API for scheduling (potentially recurring) background tasks. The service manages the lifecycle of the underlying scheduling subsystem so that other services don't need to (and avoids potential issues around multiple services attempting to initialize the same scheduling subystem in a single JVM process). The functions that are currently available are as follows: * `interspaced [interval-ms f]`: schedules a job that will call `f`, block until that call completes, sleep for `interval-ms` milliseconds, and then repeat. Returns an identifier that can be used to reference this scheduled job (e.g., for cancellation) later. * `interspaced [interval-ms f group-id]`: schedules a job that will call `f`, block until that call completes, sleep for `interval-ms` milliseconds, and then repeat. Returns an identifier that can be used to reference this scheduled job (e.g., for cancellation) later. A group identifier `group-id` can be provided that allows jobs in the same group to be stopped at the same time. * `after [interval-ms f]`: schedules a job that will call `f` a single time, after a delay of `interval-ms` milliseconds. Returns an identifier that can be used to reference this scheduled job (e.g. for cancellation) later. * `after [interval-ms f group-id]`: schedules a job that will call `f` a single time, after a delay of `interval-ms` milliseconds. Returns an identifier that can be used to reference this scheduled job (e.g. for cancellation) later. A group identifier `group-id` can be provided that allows jobs in the same group to be stopped at the same time. * `stop-job [job-id]`: Given a `job-id` returned by one of the previous functions, cancels the job. If the job is currently executing it will be allowed to complete, but will not be invoked again afterward. Returns `true` if the job was successfully stopped, `false` otherwise. * `stop-grouped-jobs [group-id]`: Given a `group-id` identifier, cancel all the jobs associated with that `group-id`. If any of the jobs are currently executing they will be allowed to complete, but will not be invoked again afterward. Returns a sequence of maps, one for each job in the group, with each map containing the `job` and a boolean `stopped?` key indiciating if the job was stopped successfully or not. * `count-jobs []`: Return a count of the total number of scheduled jobs known to to the scheduling service. `after` jobs that have completed won't be included in the total. * `count-jobs [group-id]`: Return a count of the total number of scheduled jobs with the associated `group-id` known to to the scheduling service. `after` jobs that have completed won't be included in the total. ### Implementation Details The current implementation of the `SchedulerService` is a fairly thin wrapper around the [`overtone/at-at`](https://github.com/overtone/at-at) library. This approach was chosen for a couple of reasons: * A few existing PL projects already use this library, so we thought it'd be better not to introduce a different scheduling subsystem until all of the PL TK projects are aligned to use the SchedulerService. * The `at-at` API seems like a pretty reasonable Clojure API for scheduling tasks. It would probably be a good idea to switch the implementation out to use a different backend in the future; without having done too much investigation yet, I'd be interested in looking into Quartz/Quartzite, simply because Quartz is very widely-used and battle-tested in the Java world. `at-at` does not appear to be being maintained any longer. We've had a few minor issues with it (especially w/rt shutting down the system), and haven't gotten any responses from the maintainer on the github issues we've opened. Also, the source repo for `at-at` contains no tests :( ### What's Next? * Add additional scheduling API functions, e.g. `every`. * Add API for introspecting the state of currently scheduled jobs * Consider porting the backend to something more sophisticated (and maintained) than `at-at`; if we do this, the intent would be to maintain the existing API. #Support Please log tickets and issues at our [Jira Tracker](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20Trapperkeeper). trapperkeeper-scheduler-0.1.0/dev-resources/000077500000000000000000000000001303547757500211435ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/dev-resources/logback-test.xml000066400000000000000000000006321303547757500242450ustar00rootroot00000000000000 %d %-5p [%c{2}] %m%n trapperkeeper-scheduler-0.1.0/ext/000077500000000000000000000000001303547757500171555ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/ext/travisci/000077500000000000000000000000001303547757500210015ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/ext/travisci/test.sh000077500000000000000000000000301303547757500223100ustar00rootroot00000000000000#!/bin/bash lein2 test trapperkeeper-scheduler-0.1.0/project.clj000066400000000000000000000025031303547757500205150ustar00rootroot00000000000000(def tk-version "1.1.1") (def ks-version "1.0.0") (defproject puppetlabs/trapperkeeper-scheduler "0.1.0" :description "Trapperkeeper Scheduler Service" :dependencies [[org.clojure/clojure "1.6.0"] [puppetlabs/trapperkeeper ~tk-version] [puppetlabs/kitchensink ~ks-version] [prismatic/schema "0.4.0"] [overtone/at-at "1.2.0"]] :pedantic? :abort :test-paths ["test/unit" "test/integration"] :test-selectors {:integration :integration :unit (complement :integration)} :deploy-repositories [["releases" {:url "https://clojars.org/repo" :username :env/clojars_jenkins_username :password :env/clojars_jenkins_password :sign-releases false}]] :profiles {:dev {:source-paths ["dev"] :dependencies [[org.clojure/tools.namespace "0.2.4"] [puppetlabs/trapperkeeper ~tk-version :classifier "test" :scope "test"] [puppetlabs/kitchensink ~ks-version :classifier "test" :scope "test"] [spyscope "0.1.4" :exclusions [clj-time]]] :injections [(require 'spyscope.core)]}} :repl-options {:init-ns user}) trapperkeeper-scheduler-0.1.0/src/000077500000000000000000000000001303547757500171445ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/src/puppetlabs/000077500000000000000000000000001303547757500213235ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/000077500000000000000000000000001303547757500241745ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/services/000077500000000000000000000000001303547757500260175ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/services/protocols/000077500000000000000000000000001303547757500300435ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/services/protocols/scheduler.clj000066400000000000000000000030761303547757500325210ustar00rootroot00000000000000(ns puppetlabs.trapperkeeper.services.protocols.scheduler) (defprotocol SchedulerService (interspaced [this n f] [this n f group-id] "Calls 'f' repeatedly with a delay of 'n' milliseconds between the completion of a given invocation and the beginning of the following invocation. Returns an identifier for the scheduled job. An optional group-id can be provided to collect a set of jobs into one group to allow them to be stopped together.") (after [this n f] [this n f group-id] "Calls 'f' once after a delay of 'n' milliseconds. Returns an identifier for the scheduled job. An optional group can be provided to associated jobs with each other to allow them to be stopped together.") (stop-job [this job] "Given an identifier of a scheduled job, stop its execution. If an invocation of the job is currently executing, it will be allowed to complete, but the job will not be invocated again. Returns 'true' if the job was successfully stopped, 'false' otherwise.") (stop-jobs [this] [this group-id] "Stop all the jobs associated with the service. Given an optional group-id stop only the jobs associated with that group id. Returns a sequence of maps, each with an identifier for the job and a boolean to indicate if the job was stopped successfully.") (count-jobs [this] [this group-id] "Return the number of jobs known to the scheduler service, or the number of jobs known to the scheduler service by group id. A nil group-id will return the count of all jobs.")) trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/services/scheduler/000077500000000000000000000000001303547757500277755ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/services/scheduler/scheduler_core.clj000066400000000000000000000035631303547757500334640ustar00rootroot00000000000000(ns puppetlabs.trapperkeeper.services.scheduler.scheduler-core (:require [overtone.at-at :as at-at] [clojure.tools.logging :as log])) (defn create-pool "Creates and returns a thread pool which can be used for scheduling jobs." [] (at-at/mk-pool)) (defn wrap-with-error-logging "Returns a function that will invoke 'f' inside a try/catch block. If an error occurs during execution of 'f', it will be logged and re-thrown." [f] (fn [] (try (f) (catch Throwable t (log/error t "scheduled job threw error") (throw t))))) (defn interspaced ; See docs on the service protocol, ; puppetlabs.enterprise.services.protocols.scheduler [n f pool] (let [job (wrap-with-error-logging f)] (-> (at-at/interspaced n job pool) :id ; return only the ID; do not leak the at-at RecurringJob instance ))) (defn after ; See docs on the service protocol, ; puppetlabs.enterprise.services.protocols.scheduler [n f pool] (let [job (wrap-with-error-logging f)] (-> (at-at/after n job pool) :id ; return only the ID; do not leak the at-at RecurringJob instance ))) (defn stop-job "Gracefully stops the job specified by 'id'." [id pool] (at-at/stop id pool)) (defn stop-all-jobs! "Stops all of the specified jobs." [jobs pool] (doseq [job jobs] (stop-job job pool)) ; Shutdown at-at's thread pool. This is the only way to do it, which is ; unfortunate because it also resets the thread pool. It's possible to ; hack around this via ... ; ; (-> pool ; :pool-atom ; (deref) ; :thread-pool ; (.shutdown)) ; ; ... but that is a horrible hack. I've opened an issue with at-at to add a ; function that can be called to just stop the pool and not also reset it - ; https://github.com/overtone/at-at/issues/13 (at-at/stop-and-reset-pool! pool)) trapperkeeper-scheduler-0.1.0/src/puppetlabs/trapperkeeper/services/scheduler/scheduler_service.clj000066400000000000000000000070771303547757500342000ustar00rootroot00000000000000(ns puppetlabs.trapperkeeper.services.scheduler.scheduler-service (:require [puppetlabs.trapperkeeper.services :as tk] [puppetlabs.trapperkeeper.services.protocols.scheduler :refer :all] [puppetlabs.trapperkeeper.services.scheduler.scheduler-core :as core] [clojure.tools.logging :as log])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Internal "helper" functions (defn get-pool [this] (-> this (tk/service-context) :pool)) (defn get-jobs [this] (-> this (tk/service-context) :jobs)) (defn- enqueue-job! ([this id] (enqueue-job! this id {})) ([this id opts] (let [result (assoc opts :job id)] (swap! (get-jobs this) conj result) result))) (defn- dequeue-job! ([this job] (swap! (get-jobs this) disj job)) ([this id keyword] (when-let [item (first (filter #(= id (get % keyword)) @(get-jobs this)))] (swap! (get-jobs this) disj item)))) (defn- after-job "Jobs run with `after` only execute once, and when done need to be reomved from the scheduled jobs set. This wraps the job's function so that the job is removed correctly from the set when it completes (or fails)." [this after-id f] (fn [] (try (f) (finally (dequeue-job! this after-id :after-id))))) (defn- jobs-by-group-id [this group-id] (if group-id (filter #(= group-id (:group-id %)) @(get-jobs this)) @(get-jobs this))) (defn- create-maybe-stop-job-fn "given a stop-job function, return function that when given a job returns a map with the job and a boolean to indicate if the job was stopped" [stop-fn] (fn [job] {:job job :stopped? (stop-fn job)})) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Trapperkeeper service definition (tk/defservice scheduler-service SchedulerService [] (init [this context] (log/debug "Initializing Scheduler Service") (let [pool (core/create-pool)] (assoc context :pool pool :jobs (atom #{}) :after-id (atom 0)))) (stop [this context] (log/debug "Shutting down Scheduler Service") ; Stop any jobs that are still running (core/stop-all-jobs! @(:jobs context) (get-pool this)) (log/debug "Scheduler Service shutdown complete.") context) (interspaced [this n f] (interspaced this n f nil)) (interspaced [this n f group-id] (let [id (core/interspaced n f (get-pool this))] (enqueue-job! this id {:group-id group-id}))) (after [this n f] (after this n f nil)) (after [this n f group-id] ; use after-id to identify the job for the cleanup "after-job" wrapper (let [after-id (swap! (:after-id (tk/service-context this)) inc) ; wrap the job function in a function that will remove the job from the job set when it is done wrapped-fn (after-job this after-id f) id (core/after n wrapped-fn (get-pool this))] (enqueue-job! this id {:after-id after-id :group-id group-id}))) (stop-job [this job] (let [result (core/stop-job (:job job) (get-pool this))] (dequeue-job! this job) result)) (stop-jobs [this] (stop-jobs this nil)) (stop-jobs [this group-id] (let [jobs-by-group (jobs-by-group-id this group-id)] (reduce conj [] (map (create-maybe-stop-job-fn (partial stop-job this)) jobs-by-group)))) (count-jobs [this] (count-jobs this nil)) (count-jobs [this group-id] (count (jobs-by-group-id this group-id)))) trapperkeeper-scheduler-0.1.0/test/000077500000000000000000000000001303547757500173345ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/integration/000077500000000000000000000000001303547757500216575ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/integration/puppetlabs/000077500000000000000000000000001303547757500240365ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/integration/puppetlabs/trapperkeeper/000077500000000000000000000000001303547757500267075ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/integration/puppetlabs/trapperkeeper/services/000077500000000000000000000000001303547757500305325ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/integration/puppetlabs/trapperkeeper/services/scheduler/000077500000000000000000000000001303547757500325105ustar00rootroot00000000000000scheduler_service_test.clj000066400000000000000000000343741303547757500376730ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/integration/puppetlabs/trapperkeeper/services/scheduler(ns puppetlabs.trapperkeeper.services.scheduler.scheduler-service-test (:require [clojure.test :refer :all] [puppetlabs.trapperkeeper.testutils.bootstrap :refer :all] [puppetlabs.trapperkeeper.services.scheduler.scheduler-service :refer :all] [puppetlabs.trapperkeeper.services.protocols.scheduler :refer :all] [puppetlabs.trapperkeeper.app :as tk] [overtone.at-at :as at-at])) (deftest ^:integration test-interspaced (testing "without group-id" (with-app-with-empty-config app [scheduler-service] (testing "interspaced" (let [service (tk/get-service app :SchedulerService) num-runs 3 ; let it run a few times, but not too many interval 300 p (promise) counter (atom 0) delays (atom []) last-completion-time (atom nil) job (fn [] (when @last-completion-time (let [delay (- (System/currentTimeMillis) @last-completion-time)] (swap! delays conj delay))) (swap! counter inc) ; Make this job take a while so we can measure the duration ; between invocations and ensure that the next invocation is ; not scheduled until this one completes. (Thread/sleep 100) ; The test is over! (when (= @counter num-runs) (deliver p nil)) (reset! last-completion-time (System/currentTimeMillis)))] ; Schedule the job, and wait for it run num-runs times, then stop it. (let [job-id (interspaced service interval job)] (deref p) (stop-job service job-id)) (testing (str "Each delay should be at least " interval "ms") (is (every? (fn [delay] (>= delay interval)) @delays))))))) (testing "with group-id" (with-app-with-empty-config app [scheduler-service] (testing "interspaced" (let [group-id :some-group-identifier service (tk/get-service app :SchedulerService) num-runs 3 ; let it run a few times, but not too many interval 300 p (promise) counter (atom 0) delays (atom []) last-completion-time (atom nil) job (fn [] (when @last-completion-time (let [delay (- (System/currentTimeMillis) @last-completion-time)] (swap! delays conj delay))) (swap! counter inc) ; Make this job take a while so we can measure the duration ; between invocations and ensure that the next invocation is ; not scheduled until this one completes. (Thread/sleep 100) ; The test is over! (when (= @counter num-runs) (deliver p nil)) (reset! last-completion-time (System/currentTimeMillis)))] ; Schedule the job, and wait for it run num-runs times, then stop it. (let [job-id (interspaced service interval job group-id)] (deref p) (stop-job service job-id)) (testing (str "Each delay should be at least " interval "ms") (is (every? (fn [delay] (>= delay interval)) @delays)))))))) (deftest ^:integration test-after (testing "without group-id" (with-app-with-empty-config app [scheduler-service] (testing "after" (let [delay 100] (testing "should execute at least " delay " milliseconds in the future" (let [completed (promise) job #(deliver completed (System/currentTimeMillis)) service (tk/get-service app :SchedulerService)] (let [schedule-time (System/currentTimeMillis)] (after service delay job) (let [execution-time (deref completed) actual-delay (- execution-time schedule-time)] (is (>= actual-delay delay)))))))))) (testing "with group-id" (with-app-with-empty-config app [scheduler-service] (testing "after" (let [delay 100] (testing "should execute at least " delay " milliseconds in the future" (let [completed (promise) job #(deliver completed (System/currentTimeMillis)) service (tk/get-service app :SchedulerService)] (let [schedule-time (System/currentTimeMillis)] (after service delay job :some-group-identifier) (let [execution-time (deref completed) actual-delay (- execution-time schedule-time)] (is (>= actual-delay delay))))))))))) ; This test has a race condition, but it is very unlikley to occur in reality, ; and so far it's actually been impossible to get this test to fail due to a ; lost race. 'stop-job' is probably impossible to test deterministically. ; It was decided that having a test with a race condition was better than no test ; at all in this case, primarily due to the fact that the underlying scheduler ; library (at-at) has no tests of its own. :( (deftest ^:integration test-stop-job (testing "without group-id" (testing "stop-job lets a job complete but does not run it again" (with-app-with-empty-config app [scheduler-service] (let [service (tk/get-service app :SchedulerService) started (promise) stopped (promise) start-time (atom 0) completed (promise) job (fn [] (reset! start-time (System/currentTimeMillis)) (deliver started nil) (deref stopped) (deliver completed nil)) interval 10 job-id (interspaced service interval job)] ; wait for the job to start (deref started) (let [original-start-time @start-time] (testing "the job can be stopped" (is (stop-job service job-id))) (deliver stopped nil) (deref completed) ; wait a bit, ensure the job does not run again (testing "the job should not run again" (Thread/sleep 100) (is (= original-start-time @start-time))) (testing "there should be no other jobs running" (is (= 0 (count @(get-jobs service)))))))))) (testing "with group-id" (testing "stop-job lets a job complete but does not run it again" (with-app-with-empty-config app [scheduler-service] (let [service (tk/get-service app :SchedulerService) started (promise) stopped (promise) start-time (atom 0) completed (promise) job (fn [] (reset! start-time (System/currentTimeMillis)) (deliver started nil) (deref stopped) (deliver completed nil)) interval 10 job-id (interspaced service interval job :some-group-identifier)] ; wait for the job to start (deref started) (let [original-start-time @start-time] (testing "the job can be stopped" (is (stop-job service job-id))) (deliver stopped nil) (deref completed) ; wait a bit, ensure the job does not run again (testing "the job should not run again" (Thread/sleep 100) (is (= original-start-time @start-time))) (testing "there should be no other jobs running" (is (= 0 (count @(get-jobs service))))))))))) (defn guaranteed-start-interval-job ([service interval] (guaranteed-start-interval-job service interval nil)) ([service interval group-id] (let [started (promise) job (fn [] (deliver started nil)) result (interspaced service interval job group-id)] (deref started) result))) ; This test has a few race conditions, but unlikely to occur in reality (deftest ^:integration test-count-job (testing "count-jobs shows correct number of non-group-id jobs" (with-app-with-empty-config app [scheduler-service] (let [service (tk/get-service app :SchedulerService) interval 10 job-0 (guaranteed-start-interval-job service interval)] (is (= 1 (count-jobs service))) (let [job-1 (guaranteed-start-interval-job service interval)] (is (= 2 (count-jobs service)) (let [job-2 (guaranteed-start-interval-job service interval)] (is (= 3 (count-jobs service))) (stop-job service job-0) (is (= 2 (count-jobs service))) (stop-job service job-1) (is (= 1 (count-jobs service))) (stop-job service job-2) (is (= 0 (count-jobs service))))))))) (testing "count-jobs shows correct number of group-id and non-group-id jobs" (with-app-with-empty-config app [scheduler-service] (let [service (tk/get-service app :SchedulerService) interval 10 job-0 (guaranteed-start-interval-job service interval) group-id :unique-group-id] (is (= 1 (count-jobs service))) (let [group-id-job-0 (guaranteed-start-interval-job service interval group-id)] (is (= 2 (count-jobs service))) (is (= 1 (count-jobs service group-id))) (let [job-1 (guaranteed-start-interval-job service interval)] (is (= 3 (count-jobs service))) (is (= 1 (count-jobs service group-id))) (let [group-id-job-1 (guaranteed-start-interval-job service interval group-id)] (is (= 4 (count-jobs service))) (is (= 2 (count-jobs service group-id))) (let [job-2 (guaranteed-start-interval-job service interval)] (is (= 5 (count-jobs service))) (is (= 2 (count-jobs service group-id))) (stop-job service job-0) (is (= 4 (count-jobs service))) (is (= 2 (count-jobs service group-id))) (stop-job service group-id-job-0) (is (= 3 (count-jobs service))) (is (= 1 (count-jobs service group-id))) (stop-job service job-1) (is (= 2 (count-jobs service))) (is (= 1 (count-jobs service group-id))) (stop-job service group-id-job-1) (is (= 1 (count-jobs service))) (is (= 0 (count-jobs service group-id))) (stop-job service job-2) (is (= 0 (count-jobs service))) (is (= 0 (count-jobs service group-id)))))))))) (testing "after reduces count when complete" (with-app-with-empty-config app [scheduler-service] (let [service (tk/get-service app :SchedulerService) delay 100 wait-for-start (promise) completed (promise) job (fn [] (deref wait-for-start) (deliver completed (System/currentTimeMillis)))] (after service delay job) (is (= 1 (count-jobs service))) (deliver wait-for-start true) (deref completed) ; there is a small window between when the promise is delivered and the count changes (Thread/sleep 100) (is (= 0 (count-jobs service))))))) (deftest ^:integration test-stop-grouped-jobs (testing "stop-jobs stops the jobs for a group" (with-app-with-empty-config app [scheduler-service] (let [service (tk/get-service app :SchedulerService) started (promise) job (fn [] (deliver started nil)) interval 10 group-id-0 :unique-group-id group-id-1 :more-unique-group-id ; create one job without a group-id and two with one group-id ; and a third with a different group-id job-3 (interspaced service interval (constantly true)) job-2 (interspaced service interval (constantly true) group-id-0) job-1 (interspaced service interval (constantly true) group-id-0) job-0 (interspaced service interval job group-id-1)] (testing "all the jobs were started" (is (= 4 (count-jobs service))) (is (= 2 (count-jobs service group-id-0))) (is (= 1 (count-jobs service group-id-1)))) ; wait for the jobs to start (deref started) (Thread/sleep 100) (testing "stopping one group-id does not stop them all" (stop-jobs service group-id-0) (is (= 2 (count-jobs service))) (is (= 0 (count-jobs service group-id-0))) (is (= 1 (count-jobs service group-id-1)))) (testing "stopping one job does not stop the group id based job" (stop-job service job-3) (is (= 1 (count-jobs service))) (is (= 0 (count-jobs service group-id-0))) (is (= 1 (count-jobs service group-id-1)))) (testing "stopping by group id stops the job" (stop-jobs service group-id-1)) (is (= 0 (count-jobs service))) (is (= 0 (count-jobs service group-id-0))) (is (= 0 (count-jobs service group-id-1))))))) (defn schedule-random-jobs "Schedules several random jobs and returns their at/at IDs." [service] (set (for [x [1 2 3]] (:job (interspaced service 1000 (constantly x)))))) ; In the future, it might be reasonable to add a function like this into the ; scheduler service protocol. If so, this function can be deleted. (defn at-at-scheduled-jobs "Returns all of at-at's scheduled jobs." [service] (set (map :id (at-at/scheduled-jobs (get-pool service))))) (deftest ^:integration test-shutdown (testing "Any remaining jobs will be stopped when the service is stopped." (let [app (bootstrap-services-with-empty-config [scheduler-service]) service (tk/get-service app :SchedulerService) job-ids (schedule-random-jobs service)] (testing "at-at reports all of the jobs we just scheduled" (is (= job-ids (at-at-scheduled-jobs service)))) (testing "Stopping the service stops all of the scheduled jobs" (tk/stop app) (is (= #{} (at-at-scheduled-jobs service))))))) trapperkeeper-scheduler-0.1.0/test/unit/000077500000000000000000000000001303547757500203135ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/unit/puppetlabs/000077500000000000000000000000001303547757500224725ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/unit/puppetlabs/trapperkeeper/000077500000000000000000000000001303547757500253435ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/unit/puppetlabs/trapperkeeper/services/000077500000000000000000000000001303547757500271665ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/unit/puppetlabs/trapperkeeper/services/scheduler/000077500000000000000000000000001303547757500311445ustar00rootroot00000000000000scheduler_core_test.clj000066400000000000000000000013451303547757500356070ustar00rootroot00000000000000trapperkeeper-scheduler-0.1.0/test/unit/puppetlabs/trapperkeeper/services/scheduler(ns puppetlabs.trapperkeeper.services.scheduler.scheduler-core-test (:require [clojure.test :refer :all] [puppetlabs.trapperkeeper.services.scheduler.scheduler-core :refer :all] [puppetlabs.trapperkeeper.testutils.logging :refer :all] [schema.test :as schema-test])) (use-fixtures :once schema-test/validate-schemas) (deftest wrap-with-error-logging-test (testing "when a job throws an exception, it is logged and re-thrown" (let [f #(throw (Exception. "bummer"))] (with-test-logging (is (thrown-with-msg? Exception #"bummer" ((wrap-with-error-logging f)))) (is (logged? #"scheduled job threw error" :error))))))