1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2019-2020 Bareos GmbH & Co. KG
5
6 This program is Free Software; you can redistribute it and/or
7 modify it under the terms of version three of the GNU Affero General Public
8 License as published by the Free Software Foundation, which is
9 listed in the file LICENSE.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Affero General Public License for more details.
15
16 You should have received a copy of the GNU Affero General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
20 */
21
22 #if defined(HAVE_MINGW)
23 # include "include/bareos.h"
24 # include "gtest/gtest.h"
25 #else
26 # include "gtest/gtest.h"
27 # include "include/bareos.h"
28 #endif
29
30
31 #include "dird/dird_globals.h"
32 #include "dird/jcr_private.h"
33 #include "dird/scheduler.h"
34 #include "dird/dird_conf.h"
35 #define DIRECTOR_DAEMON
36 #include "include/jcr.h"
37 #include "include/make_unique.h"
38 #include "lib/parse_conf.h"
39 #include "tests/scheduler_time_source.h"
40 #include "dird/scheduler_system_time_source.h"
41
42 #include <atomic>
43 #include <chrono>
44 #include <iostream>
45 #include <iterator>
46 #include <numeric>
47 #include <sstream>
48 #include <string>
49 #include <thread>
50
51 using namespace directordaemon;
52
53 namespace directordaemon {
DoReloadConfig()54 bool DoReloadConfig() { return false; }
55 } // namespace directordaemon
56
57 class SimulatedTimeAdapter : public SchedulerTimeAdapter {
58 public:
SimulatedTimeAdapter()59 SimulatedTimeAdapter()
60 : SchedulerTimeAdapter(std::make_unique<SimulatedTimeSource>())
61 {
62 default_wait_interval_ = 60;
63 }
64 };
65
66 static std::unique_ptr<Scheduler> scheduler;
67 static SimulatedTimeAdapter* time_adapter;
68
69 class SchedulerTest : public ::testing::Test {
SetUp()70 void SetUp() override
71 {
72 OSDependentInit();
73 std::unique_ptr<SimulatedTimeAdapter> ta
74 = std::make_unique<SimulatedTimeAdapter>();
75 time_adapter = ta.get();
76
77 scheduler = std::make_unique<Scheduler>(std::move(ta),
78 SimulatedTimeSource::ExecuteJob);
79 }
TearDown()80 void TearDown() override { scheduler.reset(); }
81 };
82
StopScheduler(std::chrono::milliseconds timeout)83 static void StopScheduler(std::chrono::milliseconds timeout)
84 {
85 std::this_thread::sleep_for(timeout);
86 scheduler->Terminate();
87 }
88
TEST_F(SchedulerTest,terminate)89 TEST_F(SchedulerTest, terminate)
90 {
91 InitMsg(NULL, NULL); /* initialize message handler */
92
93 std::string path_to_config_file
94 = std::string(RELATIVE_PROJECT_SOURCE_DIR "/configs/scheduler-hourly");
95
96 my_config = InitDirConfig(path_to_config_file.c_str(), M_ERROR_TERM);
97 ASSERT_TRUE(my_config);
98
99 my_config->ParseConfig();
100
101 std::thread scheduler_canceler(StopScheduler, std::chrono::milliseconds(200));
102
103 scheduler->Run();
104
105 scheduler_canceler.join();
106 delete my_config;
107 }
108
TEST_F(SchedulerTest,system_time_source)109 TEST_F(SchedulerTest, system_time_source)
110 {
111 SystemTimeSource s;
112 time_t start = s.SystemTime();
113 s.SleepFor(std::chrono::seconds(1));
114 time_t end = s.SystemTime();
115 EXPECT_GT(end - start, 0);
116 EXPECT_LT(end - start, 3);
117 }
118
TEST_F(SchedulerTest,system_time_source_canceled)119 TEST_F(SchedulerTest, system_time_source_canceled)
120 {
121 SystemTimeSource s;
122
123 time_t start = s.SystemTime();
124
125 std::thread terminate_wait_for_thread([&s]() {
126 std::this_thread::sleep_for(std::chrono::milliseconds(50));
127 s.Terminate();
128 });
129
130 s.SleepFor(std::chrono::seconds(1));
131 time_t end = s.SystemTime();
132
133 terminate_wait_for_thread.join();
134
135 bool end_equals_start = (end == start);
136 bool end_wrapped_to_next_second_while_waiting = (end == start + 1);
137
138 EXPECT_TRUE(end_equals_start || end_wrapped_to_next_second_while_waiting);
139 }
140
141 static std::vector<time_t> list_of_job_execution_time_stamps;
142 static std::vector<time_t> list_of_time_gaps_between_adjacent_jobs;
143 static int maximum_number_of_jobs_run{0};
144 static int counter_of_number_of_jobs_run{0};
145
ExecuteJob(JobControlRecord * jcr)146 void SimulatedTimeSource::ExecuteJob(JobControlRecord* jcr)
147 {
148 // called by the scheduler to simulate a job run
149 counter_of_number_of_jobs_run++;
150
151 list_of_job_execution_time_stamps.emplace_back(
152 time_adapter->time_source_->SystemTime());
153
154 if (debug) { std::cout << jcr->impl->res.job->resource_name_ << std::endl; }
155
156 if (counter_of_number_of_jobs_run == maximum_number_of_jobs_run) {
157 scheduler->Terminate();
158 }
159
160 // auto tm = *std::gmtime(&t);
161 // std::cout << "Executing job: " << jcr->Job << std::endl;
162 // std::cout << put_time(&tm, "%d-%m-%Y %H:%M:%S") << std::endl;
163 }
164
CalculateAverage()165 static int CalculateAverage()
166 {
167 std::adjacent_difference(
168 list_of_job_execution_time_stamps.begin(),
169 list_of_job_execution_time_stamps.end(),
170 std::back_inserter(list_of_time_gaps_between_adjacent_jobs));
171
172 list_of_time_gaps_between_adjacent_jobs.erase(
173 list_of_time_gaps_between_adjacent_jobs.begin());
174
175 int sum{std::accumulate(list_of_time_gaps_between_adjacent_jobs.begin(),
176 list_of_time_gaps_between_adjacent_jobs.end(), 0)};
177
178 return sum / list_of_time_gaps_between_adjacent_jobs.size();
179 }
180
TEST_F(SchedulerTest,hourly)181 TEST_F(SchedulerTest, hourly)
182 {
183 InitMsg(NULL, NULL);
184
185 if (debug) { std::cout << "Start test" << std::endl; }
186
187 std::string path_to_config_file{
188 std::string(RELATIVE_PROJECT_SOURCE_DIR "/configs/scheduler-hourly")};
189
190 my_config = InitDirConfig(path_to_config_file.c_str(), M_ERROR_TERM);
191 ASSERT_TRUE(my_config);
192
193 if (debug) { std::cout << "Parse config" << std::endl; }
194
195 my_config->ParseConfig();
196 ASSERT_TRUE(PopulateDefs());
197
198 list_of_job_execution_time_stamps.clear();
199 list_of_time_gaps_between_adjacent_jobs.clear();
200 counter_of_number_of_jobs_run = 0;
201 maximum_number_of_jobs_run = 25;
202
203 if (debug) { std::cout << "Run scheduler" << std::endl; }
204 scheduler->Run();
205
206 if (debug) { std::cout << "End" << std::endl; }
207 delete my_config;
208
209 int average{CalculateAverage()};
210
211 bool average_time_between_adjacent_jobs_is_too_low = average < 3600 - 36;
212 bool average_time_between_adjacent_jobs_is_too_high = average > 3600 + 36;
213
214 if (debug || average_time_between_adjacent_jobs_is_too_low
215 || average_time_between_adjacent_jobs_is_too_high) {
216 int hour = 0;
217 for (const auto& t : list_of_job_execution_time_stamps) {
218 auto tm = *std::gmtime(&t);
219 std::cout << "Hour " << hour << ": "
220 << put_time(&tm, "%d-%m-%Y %H:%M:%S");
221 if (hour) {
222 std::cout << " - " << list_of_time_gaps_between_adjacent_jobs[hour]
223 << " sec";
224 }
225 std::cout << std::endl;
226 hour++;
227 }
228 }
229
230 if (debug) {
231 std::cout << "Average simulated time between two jobs: " << average
232 << " seconds" << std::endl;
233 }
234
235 EXPECT_FALSE(average_time_between_adjacent_jobs_is_too_low);
236 EXPECT_FALSE(average_time_between_adjacent_jobs_is_too_high);
237 }
238
239
TestWithConfig(std::string path_to_config_file,std::vector<uint8_t> wdays)240 static void TestWithConfig(std::string path_to_config_file,
241 std::vector<uint8_t> wdays)
242 {
243 InitMsg(NULL, NULL);
244
245 if (debug) { std::cout << "Start test" << std::endl; }
246
247 my_config = InitDirConfig(path_to_config_file.c_str(), M_ERROR_TERM);
248 ASSERT_TRUE(my_config);
249
250 if (debug) { std::cout << "Parse config" << std::endl; }
251
252 my_config->ParseConfig();
253 ASSERT_TRUE(PopulateDefs());
254
255 list_of_job_execution_time_stamps.clear();
256 maximum_number_of_jobs_run = 3;
257 counter_of_number_of_jobs_run = 0;
258
259 if (debug) { std::cout << "Run scheduler" << std::endl; }
260 scheduler->Run();
261
262 int wday_index = 0;
263 for (const auto& t : list_of_job_execution_time_stamps) {
264 auto tm = *std::localtime(&t);
265 bool is_two_o_clock = tm.tm_hour == 2;
266
267 ASSERT_LT(wday_index, wdays.size())
268 << "List of weekdays should match the number of execution time stamps.";
269 bool is_right_day = wdays.at(wday_index) == tm.tm_wday;
270
271 EXPECT_TRUE(is_two_o_clock);
272 EXPECT_TRUE(is_right_day);
273 if (debug || !is_two_o_clock || !is_right_day) {
274 std::cout << put_time(&tm, "%A, %d-%m-%Y %H:%M:%S ") << std::endl;
275 }
276 wday_index++;
277 }
278
279 if (debug) { std::cout << "End" << std::endl; }
280 delete my_config;
281 }
282
283 enum
284 {
285 kMonday = 1,
286 kTuesday = 2,
287 kWednesday = 3,
288 kThursday = 4,
289 kFriday = 5,
290 kSaturday = 6,
291 kSunday = 7
292 };
293
TEST_F(SchedulerTest,on_time)294 TEST_F(SchedulerTest, on_time)
295 {
296 std::vector<uint8_t> wdays{kMonday, kMonday, kMonday};
297 TestWithConfig(RELATIVE_PROJECT_SOURCE_DIR "/configs/scheduler-on-time",
298 wdays);
299 }
300
TEST_F(SchedulerTest,on_time_noday)301 TEST_F(SchedulerTest, on_time_noday)
302 {
303 std::vector<uint8_t> wdays{kThursday, kFriday, kSaturday};
304 TestWithConfig(RELATIVE_PROJECT_SOURCE_DIR "/configs/scheduler-on-time-noday",
305 wdays);
306 }
307
TEST_F(SchedulerTest,on_time_noday_noclient)308 TEST_F(SchedulerTest, on_time_noday_noclient)
309 {
310 std::vector<uint8_t> wdays{kThursday, kFriday, kSaturday};
311 TestWithConfig(RELATIVE_PROJECT_SOURCE_DIR
312 "/configs/scheduler-on-time-noday-noclient",
313 wdays);
314 }
315
TEST_F(SchedulerTest,add_job_with_no_run_resource_to_queue)316 TEST_F(SchedulerTest, add_job_with_no_run_resource_to_queue)
317 {
318 InitMsg(NULL, NULL);
319
320 if (debug) { std::cout << "Start test" << std::endl; }
321
322 std::string path_to_config_file{std::string(
323 RELATIVE_PROJECT_SOURCE_DIR "/configs/bareos-configparser-tests")};
324
325 my_config = InitDirConfig(path_to_config_file.c_str(), M_ERROR_TERM);
326 ASSERT_TRUE(my_config);
327
328 if (debug) { std::cout << "Parse config" << std::endl; }
329
330 my_config->ParseConfig();
331 ASSERT_TRUE(PopulateDefs());
332
333 counter_of_number_of_jobs_run = 0;
334 maximum_number_of_jobs_run = 1;
335
336 std::thread scheduler_thread{[]() { scheduler->Run(); }};
337
338 JobResource* job{dynamic_cast<JobResource*>(
339 my_config->GetResWithName(R_JOB, "backup-bareos-fd"))};
340 ASSERT_TRUE(job) << "Job Resource \"backup-bareos-fd\" not found";
341
342 scheduler->AddJobWithNoRunResourceToQueue(job, JobTrigger::kUndefined);
343
344 scheduler_thread.join();
345 ASSERT_EQ(counter_of_number_of_jobs_run, 1);
346
347 delete my_config;
348 }
349