1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2016-2019 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 and included
9 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 * Philipp Storz, May 2016
23 */
24 /** @file
25 * responsible for doing consolidation jobs
26 *
27 * Basic tasks done here:
28 * run a virtual full job for all jobs that are configured to be always incremental
29 * based on admin.c
30 *
31 */
32
33 #include "include/bareos.h"
34 #include "dird.h"
35 #include "dird/consolidate.h"
36 #include "dird/job.h"
37 #include "dird/storage.h"
38 #include "dird/ua_input.h"
39 #include "dird/ua_server.h"
40 #include "dird/ua_run.h"
41 #include "dird/dird_globals.h"
42 #include "lib/edit.h"
43
44 namespace directordaemon {
45
46 static const int debuglevel = 100;
47
DoConsolidateInit(JobControlRecord * jcr)48 bool DoConsolidateInit(JobControlRecord *jcr)
49 {
50 FreeRstorage(jcr);
51 if (!AllowDuplicateJob(jcr)) {
52 return false;
53 }
54 return true;
55 }
56
57 /**
58 * Start a Virtual(Full) Job that creates a new virtual backup
59 * containing the jobids given in jcr->vf_jobids
60 */
StartNewConsolidationJob(JobControlRecord * jcr,char * jobname)61 static inline void StartNewConsolidationJob(JobControlRecord *jcr, char *jobname)
62 {
63 JobId_t jobid;
64 UaContext *ua;
65 PoolMem cmd(PM_MESSAGE);
66
67 ua = new_ua_context(jcr);
68 ua->batch = true;
69 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s level=VirtualFull %s", jobname, jcr->vf_jobids, jcr->accurate ? "accurate=yes" : "accurate=no");
70
71 Dmsg1(debuglevel, "=============== consolidate cmd=%s\n", ua->cmd);
72 ParseUaArgs(ua); /* parse command */
73
74 jobid = DoRunCmd(ua, ua->cmd);
75 if (jobid == 0) {
76 Jmsg(jcr, M_ERROR, 0, _("Could not start %s job.\n"), jcr->get_OperationName());
77 } else {
78 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
79 }
80
81 FreeUaContext(ua);
82 }
83
84 /**
85 * The actual consolidation worker
86 *
87 * Returns: false on failure
88 * true on success
89 */
DoConsolidate(JobControlRecord * jcr)90 bool DoConsolidate(JobControlRecord *jcr)
91 {
92 char *p;
93 JobResource *job;
94 JobResource *tmpjob;
95 bool retval = true;
96 char *jobids = NULL;
97 time_t now = time(NULL);
98 int32_t fullconsolidations_started = 0;
99 int32_t max_full_consolidations = 0;
100
101 tmpjob = jcr->res.job; /* Memorize job */
102
103 /*
104 * Get Value for MaxFullConsolidations from Consolidation job
105 */
106 max_full_consolidations = jcr->res.job->MaxFullConsolidations;
107
108 jcr->jr.JobId = jcr->JobId;
109 jcr->fname = (char *)GetPoolMemory(PM_FNAME);
110
111 /*
112 * Print Job Start message
113 */
114 Jmsg(jcr, M_INFO, 0, _("Start Consolidate JobId %d, Job=%s\n"), jcr->JobId, jcr->Job);
115
116 jcr->setJobStatus(JS_Running);
117
118 foreach_res(job, R_JOB) {
119 if (job->AlwaysIncremental) {
120 db_list_ctx jobids_ctx;
121 int32_t incrementals_total;
122 int32_t incrementals_to_consolidate;
123 int32_t max_incrementals_to_consolidate;
124
125 Jmsg(jcr, M_INFO, 0, _("Looking at always incremental job %s\n"), job->name());
126
127 /*
128 * Fake always incremental job as job of current jcr.
129 */
130 jcr->res.job = job;
131 jcr->res.fileset = job->fileset;
132 jcr->res.client = job->client;
133 jcr->jr.JobLevel = L_INCREMENTAL;
134 jcr->jr.limit = 0;
135 jcr->jr.StartTime = 0;
136
137 if (!GetOrCreateFilesetRecord(jcr)) {
138 Jmsg(jcr, M_FATAL, 0, _("JobId=%d no FileSet\n"), (int)jcr->JobId);
139 retval = false;
140 goto bail_out;
141 }
142
143 if (!GetOrCreateClientRecord(jcr)) {
144 Jmsg(jcr, M_FATAL, 0, _("JobId=%d no ClientId\n"), (int)jcr->JobId);
145 retval = false;
146 goto bail_out;
147 }
148
149 /*
150 * First determine the number of total incrementals
151 */
152 jcr->db->AccurateGetJobids(jcr, &jcr->jr, &jobids_ctx);
153 incrementals_total = jobids_ctx.count - 1;
154 Dmsg1(10, "unlimited jobids list: %s.\n", jobids_ctx.list);
155
156 /*
157 * If we are doing always incremental, we need to limit the search to
158 * only include incrementals that are older than (now - AlwaysIncrementalJobRetention)
159 */
160 if (job->AlwaysIncrementalJobRetention) {
161 char sdt[50];
162
163 jcr->jr.StartTime = now - job->AlwaysIncrementalJobRetention;
164 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
165 Jmsg(jcr, M_INFO, 0, _("%s: considering jobs older than %s for consolidation.\n"), job->name(), sdt);
166 Dmsg4(10, _("%s: considering jobs with ClientId %d and FilesetId %d older than %s for consolidation.\n"),
167 job->name(), jcr->jr.ClientId, jcr->jr.FileSetId, sdt);
168 }
169
170 jcr->db->AccurateGetJobids(jcr, &jcr->jr, &jobids_ctx);
171 Dmsg1(10, "consolidate candidates: %s.\n", jobids_ctx.list);
172
173 /**
174 * Consolidation of zero or one job does not make sense, we leave it like it is
175 */
176 if (incrementals_total < 1) {
177 Jmsg(jcr, M_INFO, 0, _("%s: less than two jobs to consolidate found, doing nothing.\n"), job->name());
178 continue;
179 }
180
181 /**
182 * Calculate limit for query. We specify how many incrementals should be left.
183 * the limit is total number of incrementals - number required - 1
184 */
185 max_incrementals_to_consolidate = incrementals_total - job->AlwaysIncrementalKeepNumber;
186
187 Dmsg2(10, "Incrementals found/required. (%d/%d).\n", incrementals_total, job->AlwaysIncrementalKeepNumber);
188 if ((max_incrementals_to_consolidate + 1 ) > 1) {
189 jcr->jr.limit = max_incrementals_to_consolidate + 1;
190 Dmsg3(10, "total: %d, to_consolidate: %d, limit: %d.\n", incrementals_total, max_incrementals_to_consolidate, jcr->jr.limit);
191 jobids_ctx.reset();
192 jcr->db->AccurateGetJobids(jcr, &jcr->jr, &jobids_ctx);
193 incrementals_to_consolidate = jobids_ctx.count - 1;
194 Dmsg2(10, "%d consolidate ids after limit: %s.\n", jobids_ctx.count, jobids_ctx.list);
195 if (incrementals_to_consolidate < 1) {
196 Jmsg(jcr, M_INFO, 0, _("%s: After limited query: less incrementals than required, not consolidating\n"), job->name());
197 continue;
198 }
199 } else {
200 Jmsg(jcr, M_INFO, 0, _("%s: less incrementals than required, not consolidating\n"), job->name());
201 continue;
202 }
203
204 if (jobids) {
205 free(jobids);
206 jobids = NULL;
207 }
208
209 jobids = bstrdup(jobids_ctx.list);
210 p = jobids;
211
212 /**
213 * Check if we need to skip the first (full) job from consolidation
214 */
215 if (job->AlwaysIncrementalMaxFullAge) {
216 char sdt_allowed[50];
217 char sdt_starttime[50];
218 time_t starttime,
219 oldest_allowed_starttime;
220
221 if (incrementals_to_consolidate < 2) {
222 Jmsg(jcr, M_INFO, 0, _("%s: less incrementals than required to consolidate without full, not consolidating\n"), job->name());
223 continue;
224 }
225 Jmsg(jcr, M_INFO, 0, _("before ConsolidateFull: jobids: %s\n"), jobids);
226
227 p = strchr(jobids, ','); /* find oldest jobid and optionally skip it */
228 if (p) {
229 *p = '\0';
230 }
231
232 /**
233 * Get db record of oldest jobid and check its age
234 */
235 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
236 jcr->previous_jr.JobId = str_to_int64(jobids);
237 Dmsg1(10, "Previous JobId=%s\n", jobids);
238
239 if (!jcr->db->GetJobRecord(jcr, &jcr->previous_jr)) {
240 Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for first Job: ERR=%s"), jcr->db->strerror());
241 goto bail_out;
242 }
243
244 starttime = jcr->previous_jr.JobTDate;
245 oldest_allowed_starttime = now - job->AlwaysIncrementalMaxFullAge;
246 bstrftimes(sdt_allowed, sizeof(sdt_allowed), oldest_allowed_starttime);
247 bstrftimes(sdt_starttime, sizeof(sdt_starttime), starttime);
248
249 /**
250 * Check if job is older than AlwaysIncrementalMaxFullAge
251 */
252 Jmsg(jcr, M_INFO, 0, _("check full age: full is %s, allowed is %s\n"), sdt_starttime, sdt_allowed);
253 if (starttime > oldest_allowed_starttime) {
254 Jmsg(jcr, M_INFO, 0, _("Full is newer than AlwaysIncrementalMaxFullAge -> skipping first jobid %s because of age\n"), jobids);
255 if (p) {
256 *p++ = ','; /* Restore , and point to rest of list */
257 }
258
259 } else if (max_full_consolidations &&
260 fullconsolidations_started >= max_full_consolidations) {
261 Jmsg(jcr, M_INFO, 0, _("%d AlwaysIncrementalFullConsolidations reached -> skipping first jobid %s independent of age\n"),
262 max_full_consolidations, jobids);
263 if (p) {
264 *p++ = ','; /* Restore , and point to rest of list */
265 }
266
267 } else {
268 Jmsg(jcr, M_INFO, 0, _("Full is older than AlwaysIncrementalMaxFullAge -> also consolidating Full jobid %s\n"), jobids);
269 if (p) {
270 *p = ','; /* Restore ,*/
271 p = jobids; /* Point to full list */
272 }
273 fullconsolidations_started++;
274 }
275 Jmsg(jcr, M_INFO, 0, _("after ConsolidateFull: jobids: %s\n"), p);
276 }
277
278 /**
279 * Set the virtualfull jobids to be consolidated
280 */
281 if (!jcr->vf_jobids) {
282 jcr->vf_jobids = GetPoolMemory(PM_MESSAGE);
283 }
284 PmStrcpy(jcr->vf_jobids, p);
285
286 Jmsg(jcr, M_INFO, 0, _("%s: Start new consolidation\n"), job->name());
287 StartNewConsolidationJob(jcr, job->name());
288 }
289 }
290
291 bail_out:
292 /**
293 * Restore original job back to jcr.
294 */
295 jcr->res.job = tmpjob;
296 jcr->setJobStatus(JS_Terminated);
297 ConsolidateCleanup(jcr, JS_Terminated);
298
299 if (jobids) {
300 free(jobids);
301 }
302
303 return retval;
304 }
305
306 /**
307 * Release resources allocated during backup.
308 */
ConsolidateCleanup(JobControlRecord * jcr,int TermCode)309 void ConsolidateCleanup(JobControlRecord *jcr, int TermCode)
310 {
311 int msg_type;
312 char term_code[100];
313 const char *TermMsg;
314 char sdt[50], edt[50], schedt[50];
315
316 Dmsg0(debuglevel, "Enter backup_cleanup()\n");
317
318 UpdateJobEnd(jcr, TermCode);
319
320 if (!jcr->db->GetJobRecord(jcr, &jcr->jr)) {
321 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"), jcr->db->strerror());
322 jcr->setJobStatus(JS_ErrorTerminated);
323 }
324
325 msg_type = M_INFO; /* by default INFO message */
326 switch (jcr->JobStatus) {
327 case JS_Terminated:
328 TermMsg = _("Consolidate OK");
329 break;
330 case JS_FatalError:
331 case JS_ErrorTerminated:
332 TermMsg = _("*** Consolidate Error ***");
333 msg_type = M_ERROR; /* Generate error message */
334 break;
335 case JS_Canceled:
336 TermMsg = _("Consolidate Canceled");
337 break;
338 default:
339 TermMsg = term_code;
340 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
341 break;
342 }
343 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
344 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
345 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
346
347 Jmsg(jcr, msg_type, 0, _("BAREOS " VERSION " (" LSMDATE "): %s\n"
348 " JobId: %d\n"
349 " Job: %s\n"
350 " Scheduled time: %s\n"
351 " Start time: %s\n"
352 " End time: %s\n"
353 " Bareos binary info: %s\n"
354 " Termination: %s\n\n"),
355 edt,
356 jcr->jr.JobId,
357 jcr->jr.Job,
358 schedt,
359 sdt,
360 edt,
361 BAREOS_JOBLOG_MESSAGE,
362 TermMsg);
363
364 Dmsg0(debuglevel, "Leave ConsolidateCleanup()\n");
365 }
366 } /* namespace directordaemon */
367