1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2012 Planets Communications B.V.
6 Copyright (C) 2013-2018 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Kern Sibbald, May MM, major revision December MMIII
25 */
26 /**
27 * @file
28 * BAREOS scheduler
29 *
30 * It looks at what jobs are to be run and when
31 * and waits around until it is time to
32 * fire them up.
33 */
34
35 #include "include/bareos.h"
36 #include "dird.h"
37 #include "dird/dird_globals.h"
38 #include "dird/job.h"
39 #include "dird/storage.h"
40
41 #if 0
42 #define SCHED_DEBUG
43 #define DBGLVL 0
44 #else
45 #undef SCHED_DEBUG
46 #define DBGLVL 200
47 #endif
48
49 namespace directordaemon {
50
51 const int debuglevel = DBGLVL;
52
53 /* Local variables */
54 struct job_item {
55 RunResource *run;
56 JobResource *job;
57 time_t runtime;
58 int Priority;
59 dlink link; /* link for list */
60 };
61
62 /* List of jobs to be run. They were scheduled in this hour or the next */
63 static dlist *jobs_to_run; /* list of jobs to be run */
64
65 /* Time interval in secs to sleep if nothing to be run */
66 static int const next_check_secs = 60;
67
68 /* Forward referenced subroutines */
69 static void find_runs();
70 static void add_job(JobResource *job, RunResource *run, time_t now, time_t runtime);
71 static void dump_job(job_item *ji, const char *msg);
72
73 /* Imported subroutines */
74
75 /* Imported variables */
76
77 /**
78 * called by reload_config to tell us that the schedules
79 * we may have based our next jobs to run queues have been
80 * invalidated. In fact the schedules may not have changed
81 * but the run object that we have recorded the last_run time
82 * on are new and no longer have a valid last_run time which
83 * causes us to double run schedules that get put into the list
84 * because run_nh = 1.
85 */
86 static bool schedules_invalidated = false;
InvalidateSchedules(void)87 void InvalidateSchedules(void) {
88 schedules_invalidated = true;
89 }
90
91 /**
92 *
93 * Main Bareos Scheduler
94 *
95 */
wait_for_next_job(char * one_shot_job_to_run)96 JobControlRecord *wait_for_next_job(char *one_shot_job_to_run)
97 {
98 JobControlRecord *jcr;
99 JobResource *job;
100 RunResource *run;
101 time_t now, prev;
102 static bool first = true;
103 job_item *next_job = NULL;
104
105 Dmsg0(debuglevel, "Enter wait_for_next_job\n");
106 if (first) {
107 first = false;
108 /* Create scheduled jobs list */
109 jobs_to_run = New(dlist(next_job, &next_job->link));
110 if (one_shot_job_to_run) { /* one shot */
111 job = (JobResource *)my_config->GetResWithName(R_JOB, one_shot_job_to_run);
112 if (!job) {
113 Emsg1(M_ABORT, 0, _("Job %s not found\n"), one_shot_job_to_run);
114 }
115 Dmsg1(5, "Found one_shot_job_to_run %s\n", one_shot_job_to_run);
116 jcr = new_jcr(sizeof(JobControlRecord), DirdFreeJcr);
117 SetJcrDefaults(jcr, job);
118 return jcr;
119 }
120 }
121
122 /* Wait until we have something in the
123 * next hour or so.
124 */
125 again:
126 while (jobs_to_run->empty()) {
127 find_runs();
128 if (!jobs_to_run->empty()) {
129 break;
130 }
131 Bmicrosleep(next_check_secs, 0); /* recheck once per minute */
132 }
133
134 #ifdef list_chain
135 job_item *je;
136 foreach_dlist(je, jobs_to_run) {
137 dump_job(je, _("Walk queue"));
138 }
139 #endif
140 /*
141 * Pull the first job to run (already sorted by runtime and
142 * Priority, then wait around until it is time to run it.
143 */
144 next_job = (job_item *)jobs_to_run->first();
145 jobs_to_run->remove(next_job);
146
147 dump_job(next_job, _("Dequeued job"));
148
149 if (!next_job) { /* we really should have something now */
150 Emsg0(M_ABORT, 0, _("Scheduler logic error\n"));
151 }
152
153 /* Now wait for the time to run the job */
154 for (;;) {
155 time_t twait;
156 /* discard scheduled queue and rebuild with new schedule objects. */
157 LockJobs();
158 if (schedules_invalidated) {
159 dump_job(next_job, "Invalidated job");
160 free(next_job);
161 while (!jobs_to_run->empty()) {
162 next_job = (job_item *)jobs_to_run->first();
163 jobs_to_run->remove(next_job);
164 dump_job(next_job, "Invalidated job");
165 free(next_job);
166 }
167 schedules_invalidated = false;
168 UnlockJobs();
169 goto again;
170 }
171 UnlockJobs();
172 prev = now = time(NULL);
173 twait = next_job->runtime - now;
174 if (twait <= 0) { /* time to run it */
175 break;
176 }
177 /* Recheck at least once per minute */
178 Bmicrosleep((next_check_secs < twait)?next_check_secs:twait, 0);
179 /* Attempt to handle clock shift (but not daylight savings time changes)
180 * we allow a skew of 10 seconds before invalidating everything.
181 */
182 now = time(NULL);
183 if (now < prev-10 || now > (prev+next_check_secs+10)) {
184 schedules_invalidated = true;
185 }
186 }
187
188 jcr = new_jcr(sizeof(JobControlRecord), DirdFreeJcr);
189 run = next_job->run; /* pick up needed values */
190 job = next_job->job;
191
192 if (job->enabled && (!job->client || job->client->enabled)) {
193 dump_job(next_job, _("Run job"));
194 }
195
196 free(next_job);
197
198 if (!job->enabled ||
199 (job->schedule && !job->schedule->enabled) ||
200 (job->client && !job->client->enabled)) {
201 FreeJcr(jcr);
202 goto again; /* ignore this job */
203 }
204
205 run->last_run = now; /* mark as run now */
206
207 ASSERT(job);
208 SetJcrDefaults(jcr, job);
209 if (run->level) {
210 jcr->setJobLevel(run->level); /* override run level */
211 }
212
213 if (run->pool) {
214 jcr->res.pool = run->pool; /* override pool */
215 jcr->res.run_pool_override = true;
216 }
217
218 if (run->full_pool) {
219 jcr->res.full_pool = run->full_pool; /* override full pool */
220 jcr->res.run_full_pool_override = true;
221 }
222
223 if (run->vfull_pool) {
224 jcr->res.vfull_pool = run->vfull_pool; /* override virtual full pool */
225 jcr->res.run_vfull_pool_override = true;
226 }
227
228 if (run->inc_pool) {
229 jcr->res.inc_pool = run->inc_pool; /* override inc pool */
230 jcr->res.run_inc_pool_override = true;
231 }
232
233 if (run->diff_pool) {
234 jcr->res.diff_pool = run->diff_pool; /* override diff pool */
235 jcr->res.run_diff_pool_override = true;
236 }
237
238 if (run->next_pool) {
239 jcr->res.next_pool = run->next_pool; /* override next pool */
240 jcr->res.run_next_pool_override = true;
241 }
242
243 if (run->storage) {
244 UnifiedStorageResource store;
245 store.store = run->storage;
246 PmStrcpy(store.store_source, _("run override"));
247 SetRwstorage(jcr, &store); /* override storage */
248 }
249
250 if (run->msgs) {
251 jcr->res.messages = run->msgs; /* override messages */
252 }
253
254 if (run->Priority) {
255 jcr->JobPriority = run->Priority;
256 }
257
258 if (run->spool_data_set) {
259 jcr->spool_data = run->spool_data;
260 }
261
262 if (run->accurate_set) {
263 jcr->accurate = run->accurate; /* overwrite accurate mode */
264 }
265
266 if (run->MaxRunSchedTime_set) {
267 jcr->MaxRunSchedTime = run->MaxRunSchedTime;
268 }
269
270 Dmsg0(debuglevel, "Leave wait_for_next_job()\n");
271 return jcr;
272 }
273
274 /**
275 * Shutdown the scheduler
276 */
TermScheduler()277 void TermScheduler()
278 {
279 if (jobs_to_run) {
280 delete jobs_to_run;
281 }
282 }
283
284 /**
285 * check if given day of year is in last week of the month in the current year
286 * depending if the year is leap year or not, the doy of the last day of the month
287 * is varying one day.
288 */
IsDoyInLastWeek(int year,int doy)289 bool IsDoyInLastWeek(int year, int doy)
290 {
291 int i;
292 int *last_dom;
293 int last_day_of_month[] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
294 int last_day_of_month_leap[] = { 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
295
296 /*
297 * Determine if this is a leap year.
298 */
299 if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
300 last_dom = last_day_of_month_leap;
301 } else {
302 last_dom = last_day_of_month;
303 }
304
305 for (i = 0; i < 12; i++) {
306 /* doy is zero-based */
307 if (doy > ((last_dom[i] - 1) - 7) && doy <= (last_dom[i] - 1)) {
308 return true;
309 }
310 }
311
312 return false;
313 }
314
315 /**
316 * Find all jobs to be run this hour and the next hour.
317 */
find_runs()318 static void find_runs()
319 {
320 time_t now, next_hour, runtime;
321 RunResource *run;
322 JobResource *job;
323 ScheduleResource *sched;
324 struct tm tm;
325 bool is_last_week = false; /* are we in the last week of a month? */
326 bool nh_is_last_week = false; /* are we in the last week of a month? */
327 int hour, mday, wday, month, wom, woy, yday;
328 /* Items corresponding to above at the next hour */
329 int nh_hour, nh_mday, nh_wday, nh_month, nh_wom, nh_woy, nh_yday;
330
331 Dmsg0(debuglevel, "enter find_runs()\n");
332
333 /*
334 * Compute values for time now
335 */
336 now = time(NULL);
337 Blocaltime(&now, &tm);
338 hour = tm.tm_hour;
339 mday = tm.tm_mday - 1;
340 wday = tm.tm_wday;
341 month = tm.tm_mon;
342 wom = mday / 7;
343 woy = TmWoy(now); /* get week of year */
344 yday = tm.tm_yday; /* get day of year */
345
346 Dmsg8(debuglevel, "now = %x: h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
347 now, hour, month, mday, wday, wom, woy, yday);
348
349 is_last_week = IsDoyInLastWeek(tm.tm_year + 1900 , yday);
350
351 /*
352 * Compute values for next hour from now.
353 * We do this to be sure we don't miss a job while
354 * sleeping.
355 */
356 next_hour = now + 3600;
357 Blocaltime(&next_hour, &tm);
358 nh_hour = tm.tm_hour;
359 nh_mday = tm.tm_mday - 1;
360 nh_wday = tm.tm_wday;
361 nh_month = tm.tm_mon;
362 nh_wom = nh_mday / 7;
363 nh_woy = TmWoy(next_hour); /* get week of year */
364 nh_yday = tm.tm_yday; /* get day of year */
365
366 Dmsg8(debuglevel, "nh = %x: h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
367 next_hour, nh_hour, nh_month, nh_mday, nh_wday, nh_wom, nh_woy, nh_yday);
368
369 nh_is_last_week = IsDoyInLastWeek(tm.tm_year + 1900 , nh_yday);
370
371 /*
372 * Loop through all jobs
373 */
374 LockRes(my_config);
375 foreach_res(job, R_JOB) {
376 sched = job->schedule;
377 if (sched == NULL ||
378 !sched->enabled ||
379 !job->enabled ||
380 (job->client && !job->client->enabled)) { /* scheduled? or enabled? */
381 continue; /* no, skip this job */
382 }
383
384 Dmsg1(debuglevel, "Got job: %s\n", job->hdr.name);
385 for (run = sched->run; run; run = run->next) {
386 bool run_now, run_nh;
387 /*
388 * Find runs scheduled between now and the next hour.
389 */
390 #ifdef xxxx
391 Dmsg0(000, "\n");
392 Dmsg7(000, "run h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
393 hour, month, mday, wday, wom, woy, yday);
394 Dmsg6(000, "bitset bsh=%d bsm=%d bsmd=%d bswd=%d bswom=%d bswoy=%d\n",
395 BitIsSet(hour, run->hour),
396 BitIsSet(month, run->month),
397 BitIsSet(mday, run->mday),
398 BitIsSet(wday, run->wday),
399 BitIsSet(wom, run->wom),
400 BitIsSet(woy, run->woy));
401
402 Dmsg7(000, "nh_run h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
403 nh_hour, nh_month, nh_mday, nh_wday, nh_wom, nh_woy, nh_yday);
404 Dmsg6(000, "nh_bitset bsh=%d bsm=%d bsmd=%d bswd=%d bswom=%d bswoy=%d\n",
405 BitIsSet(nh_hour, run->hour),
406 BitIsSet(nh_month, run->month),
407 BitIsSet(nh_mday, run->mday),
408 BitIsSet(nh_wday, run->wday),
409 BitIsSet(nh_wom, run->wom),
410 BitIsSet(nh_woy, run->woy));
411 Dmsg2(000, "run->last_set:%d, is_last_week:%d\n", run->last_set, is_last_week);
412 Dmsg2(000, "run->last_set:%d, nh_is_last_week:%d\n", run->last_set, nh_is_last_week);
413 #endif
414
415 run_now = BitIsSet(hour, run->hour) &&
416 BitIsSet(mday, run->mday) &&
417 BitIsSet(wday, run->wday) &&
418 BitIsSet(month, run->month) &&
419 (BitIsSet(wom, run->wom) || (run->last_set && is_last_week)) &&
420 BitIsSet(woy, run->woy);
421
422 run_nh = BitIsSet(nh_hour, run->hour) &&
423 BitIsSet(nh_mday, run->mday) &&
424 BitIsSet(nh_wday, run->wday) &&
425 BitIsSet(nh_month, run->month) &&
426 (BitIsSet(nh_wom, run->wom) || (run->last_set && nh_is_last_week)) &&
427 BitIsSet(nh_woy, run->woy);
428
429 Dmsg3(debuglevel, "run@%p: run_now=%d run_nh=%d\n", run, run_now, run_nh);
430
431 if (run_now || run_nh) {
432 /*
433 * find time (time_t) job is to be run
434 */
435 Blocaltime(&now, &tm); /* reset tm structure */
436 tm.tm_min = run->minute; /* set run minute */
437 tm.tm_sec = 0; /* zero secs */
438 runtime = mktime(&tm);
439 if (run_now) {
440 add_job(job, run, now, runtime);
441 }
442 /* If job is to be run in the next hour schedule it */
443 if (run_nh) {
444 add_job(job, run, now, runtime + 3600);
445 }
446 }
447 }
448 }
449 UnlockRes(my_config);
450 Dmsg0(debuglevel, "Leave find_runs()\n");
451 }
452
add_job(JobResource * job,RunResource * run,time_t now,time_t runtime)453 static void add_job(JobResource *job, RunResource *run, time_t now, time_t runtime)
454 {
455 job_item *ji;
456 bool inserted = false;
457 /*
458 * Don't run any job that ran less than a minute ago, but
459 * do run any job scheduled less than a minute ago.
460 */
461 if (((runtime - run->last_run) < 61) || ((runtime+59) < now)) {
462 #ifdef SCHED_DEBUG
463 Dmsg4(000, "Drop: Job=\"%s\" run=%lld. last_run=%lld. now=%lld\n", job->hdr.name,
464 (utime_t)runtime, (utime_t)run->last_run, (utime_t)now);
465 fflush(stdout);
466 #endif
467 return;
468 }
469 #ifdef SCHED_DEBUG
470 Dmsg4(000, "Add: Job=\"%s\" run=%lld last_run=%lld now=%lld\n", job->hdr.name,
471 (utime_t)runtime, (utime_t)run->last_run, (utime_t)now);
472 #endif
473 /* accept to run this job */
474 job_item *je = (job_item *)malloc(sizeof(job_item));
475 je->run = run;
476 je->job = job;
477 je->runtime = runtime;
478 if (run->Priority) {
479 je->Priority = run->Priority;
480 } else {
481 je->Priority = job->Priority;
482 }
483
484 /* Add this job to the wait queue in runtime, priority sorted order */
485 foreach_dlist(ji, jobs_to_run) {
486 if (ji->runtime > je->runtime ||
487 (ji->runtime == je->runtime && ji->Priority > je->Priority)) {
488 jobs_to_run->InsertBefore(je, ji);
489 dump_job(je, _("Inserted job"));
490 inserted = true;
491 break;
492 }
493 }
494 /* If place not found in queue, append it */
495 if (!inserted) {
496 jobs_to_run->append(je);
497 dump_job(je, _("Appended job"));
498 }
499 #ifdef SCHED_DEBUG
500 foreach_dlist(ji, jobs_to_run) {
501 dump_job(ji, _("Run queue"));
502 }
503 Dmsg0(000, "End run queue\n");
504 #endif
505 }
506
dump_job(job_item * ji,const char * msg)507 static void dump_job(job_item *ji, const char *msg)
508 {
509 #ifdef SCHED_DEBUG
510 char dt[MAX_TIME_LENGTH];
511 int save_debug = debug_level;
512 if (debug_level < debuglevel) {
513 return;
514 }
515 bstrftime_nc(dt, sizeof(dt), ji->runtime);
516 Dmsg4(debuglevel, "%s: Job=%s priority=%d run %s\n", msg, ji->job->hdr.name,
517 ji->Priority, dt);
518 fflush(stdout);
519 debug_level = save_debug;
520 #endif
521 }
522 } /* namespace directordaemon */
523