1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2001-2012 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 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, March MMI
25 */
26 /**
27 * @file
28 * handles finding the next volume for append.
29 *
30 * Split out of catreq.c August MMIII catalog request from the Storage daemon.
31 */
32
33 #include "include/bareos.h"
34 #include "dird.h"
35 #include "dird/autoprune.h"
36 #include "dird/autorecycle.h"
37 #include "dird/next_vol.h"
38 #include "dird/newvol.h"
39 #include "dird/ua_db.h"
40 #include "dird/ua_server.h"
41 #include "dird/ua_prune.h"
42 #include "dird/ua_purge.h"
43 #include "lib/edit.h"
44
45 namespace directordaemon {
46
47 static int const debuglevel = 50; /* debug level */
48
49 /**
50 * Set storage id if possible
51 */
SetStorageidInMr(StorageResource * store,MediaDbRecord * mr)52 void SetStorageidInMr(StorageResource *store, MediaDbRecord *mr)
53 {
54 if (store != NULL) {
55 mr->StorageId = store->StorageId;
56 }
57 }
58
59 /**
60 * Items needed:
61 *
62 * mr.PoolId must be set
63 * mr.ScratchPoolId could be set (used if create==true)
64 * jcr->write_storage
65 * jcr->db
66 * jcr->pool
67 * MediaDbRecord mr with PoolId set
68 * unwanted_volumes -- list of volumes we don't want
69 * create -- whether or not to create a new volume
70 * prune -- whether or not to prune volumes
71 */
FindNextVolumeForAppend(JobControlRecord * jcr,MediaDbRecord * mr,int index,const char * unwanted_volumes,bool create,bool prune)72 int FindNextVolumeForAppend(JobControlRecord *jcr, MediaDbRecord *mr, int index,
73 const char *unwanted_volumes, bool create, bool prune)
74 {
75 int retry = 0;
76 bool ok;
77 bool InChanger;
78 StorageResource *store = jcr->res.write_storage;
79
80 bstrncpy(mr->MediaType, store->media_type, sizeof(mr->MediaType));
81 Dmsg3(debuglevel, "find_next_vol_for_append: JobId=%u PoolId=%d, MediaType=%s\n",
82 (uint32_t)jcr->JobId, (int)mr->PoolId, mr->MediaType);
83
84 /*
85 * If we are using an Autochanger, restrict Volume search to the Autochanger on the first pass
86 */
87 InChanger = store->autochanger;
88
89 /*
90 * Find the Next Volume for Append
91 */
92 DbLock(jcr->db);
93 while (1) {
94 /*
95 * 1. Look for volume with "Append" status.
96 */
97 SetStorageidInMr(store, mr);
98
99 bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus));
100 ok = jcr->db->FindNextVolume(jcr, index, InChanger, mr, unwanted_volumes);
101 if (!ok) {
102 /*
103 * No volume found, apply algorithm
104 */
105 Dmsg4(debuglevel, "after find_next_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
106 ok, index, InChanger, mr->VolStatus);
107
108 /*
109 * 2. Try finding a recycled volume
110 */
111 ok = FindRecycledVolume(jcr, InChanger, mr, store, unwanted_volumes);
112 SetStorageidInMr(store, mr);
113 Dmsg2(debuglevel, "FindRecycledVolume ok=%d FW=%d\n", ok, mr->FirstWritten);
114 if (!ok) {
115 /*
116 * 3. Try recycling any purged volume
117 */
118 ok = RecycleOldestPurgedVolume(jcr, InChanger, mr, store, unwanted_volumes);
119 SetStorageidInMr(store, mr);
120 if (!ok) {
121 /*
122 * 4. Try pruning Volumes
123 */
124 if (prune) {
125 Dmsg0(debuglevel, "Call PruneVolumes\n");
126 PruneVolumes(jcr, InChanger, mr, store);
127 }
128 ok = RecycleOldestPurgedVolume(jcr, InChanger, mr, store, unwanted_volumes);
129 SetStorageidInMr(store, mr); /* put StorageId in new record */
130 if (!ok && create) {
131 Dmsg4(debuglevel, "after prune volumes_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
132 ok, index, InChanger, mr->VolStatus);
133 /*
134 * 5. Try pulling a volume from the Scratch pool
135 */
136 ok = GetScratchVolume(jcr, InChanger, mr, store);
137 SetStorageidInMr(store, mr); /* put StorageId in new record */
138 Dmsg4(debuglevel, "after get scratch volume ok=%d index=%d InChanger=%d Vstat=%s\n",
139 ok, index, InChanger, mr->VolStatus);
140 }
141 /*
142 * If we are using an Autochanger and have not found
143 * a volume, retry looking for any volume.
144 */
145 if (!ok && InChanger) {
146 InChanger = false;
147 continue; /* retry again accepting any volume */
148 }
149 }
150 }
151
152 if (!ok && create) {
153 /*
154 * 6. Try "creating" a new Volume
155 */
156 ok = newVolume(jcr, mr, store);
157 }
158
159 /*
160 * Look at more drastic ways to find an Appendable Volume
161 */
162 if (!ok && (jcr->res.pool->purge_oldest_volume ||
163 jcr->res.pool->recycle_oldest_volume)) {
164 Dmsg2(debuglevel, "No next volume found. PurgeOldest=%d\n RecyleOldest=%d",
165 jcr->res.pool->purge_oldest_volume, jcr->res.pool->recycle_oldest_volume);
166
167 /*
168 * Find oldest volume to recycle
169 */
170 SetStorageidInMr(store, mr);
171 ok = jcr->db->FindNextVolume(jcr, -1, InChanger, mr, unwanted_volumes);
172 SetStorageidInMr(store, mr);
173 Dmsg1(debuglevel, "Find oldest=%d Volume\n", ok);
174 if (ok && prune) {
175 UaContext *ua;
176 Dmsg0(debuglevel, "Try purge Volume.\n");
177 /*
178 * 7. Try to purging oldest volume only if not UA calling us.
179 */
180 ua = new_ua_context(jcr);
181 if (jcr->res.pool->purge_oldest_volume && create) {
182 Jmsg(jcr, M_INFO, 0, _("Purging oldest volume \"%s\"\n"), mr->VolumeName);
183 ok = PurgeJobsFromVolume(ua, mr);
184 } else if (jcr->res.pool->recycle_oldest_volume) {
185 /*
186 * 8. Try recycling the oldest volume
187 */
188 Jmsg(jcr, M_INFO, 0, _("Pruning oldest volume \"%s\"\n"), mr->VolumeName);
189 ok = PruneVolume(ua, mr);
190 }
191 FreeUaContext(ua);
192
193 if (ok) {
194 ok = RecycleVolume(jcr, mr);
195 Dmsg1(debuglevel, "Recycle after purge oldest=%d\n", ok);
196 }
197 }
198 }
199 }
200
201 Dmsg2(debuglevel, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
202 if (ok) {
203 /*
204 * If we can use the volume, check if it is expired
205 */
206 if (bstrcmp(mr->VolStatus, "Append") && HasVolumeExpired(jcr, mr)) {
207 if (retry++ < 200) { /* sanity check */
208 continue; /* try again from the top */
209 } else {
210 Jmsg(jcr, M_ERROR, 0, _("We seem to be looping trying to find the next volume. I give up.\n"));
211 }
212 }
213 }
214
215 break;
216 }
217
218 DbUnlock(jcr->db);
219 Dmsg1(debuglevel, "return ok=%d find_next_vol\n", ok);
220
221 return ok;
222 }
223
224 /**
225 * Check if any time limits or use limits have expired if so,
226 * set the VolStatus appropriately.
227 */
HasVolumeExpired(JobControlRecord * jcr,MediaDbRecord * mr)228 bool HasVolumeExpired(JobControlRecord *jcr, MediaDbRecord *mr)
229 {
230 bool expired = false;
231 char ed1[50];
232
233 /*
234 * Check limits and expirations if "Append" and it has been used i.e. mr->VolJobs > 0
235 */
236 if (bstrcmp(mr->VolStatus, "Append") && mr->VolJobs > 0) {
237 /*
238 * First handle Max Volume Bytes
239 */
240 if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
241 Jmsg(jcr, M_INFO, 0, _("Max Volume bytes=%s exceeded. Marking Volume \"%s\" as Full.\n"),
242 edit_uint64_with_commas(mr->MaxVolBytes, ed1), mr->VolumeName);
243 bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
244 expired = true;
245 } else if (mr->VolBytes > 0 && jcr->res.pool->use_volume_once) {
246 /*
247 * Volume should only be used once
248 */
249 Jmsg(jcr, M_INFO, 0, _("Volume used once. Marking Volume \"%s\" as Used.\n"),
250 mr->VolumeName);
251 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
252 expired = true;
253 } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
254 /*
255 * Max Jobs written to volume
256 */
257 Jmsg(jcr, M_INFO, 0, _("Max Volume jobs=%s exceeded. Marking Volume \"%s\" as Used.\n"),
258 edit_uint64_with_commas(mr->MaxVolJobs, ed1), mr->VolumeName);
259 Dmsg3(debuglevel, "MaxVolJobs=%d JobId=%d Vol=%s\n", mr->MaxVolJobs,
260 (uint32_t)jcr->JobId, mr->VolumeName);
261 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
262 expired = true;
263 } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
264 /*
265 * Max Files written to volume
266 */
267 Jmsg(jcr, M_INFO, 0, _("Max Volume files=%s exceeded. Marking Volume \"%s\" as Used.\n"),
268 edit_uint64_with_commas(mr->MaxVolFiles, ed1), mr->VolumeName);
269 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
270 expired = true;
271 } else if (mr->VolUseDuration > 0) {
272 /*
273 * Use duration expiration
274 */
275 utime_t now = time(NULL);
276 if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
277 Jmsg(jcr, M_INFO, 0, _("Max configured use duration=%s sec. exceeded. Marking Volume \"%s\" as Used.\n"),
278 edit_uint64_with_commas(mr->VolUseDuration, ed1), mr->VolumeName);
279 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
280 expired = true;
281 }
282 }
283 }
284
285 if (expired) {
286 /*
287 * Need to update media
288 */
289 Dmsg1(debuglevel, "Vol=%s has expired update media record\n", mr->VolumeName);
290 SetStorageidInMr(NULL, mr);
291 if (!jcr->db->UpdateMediaRecord(jcr, mr)) {
292 Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
293 mr->VolumeName, jcr->db->strerror());
294 }
295 }
296 Dmsg2(debuglevel, "Vol=%s expired=%d\n", mr->VolumeName, expired);
297
298 return expired;
299 }
300
301 /**
302 * Try hard to recycle the current volume
303 *
304 * Returns: on failure - reason = NULL
305 * on success - reason - pointer to reason
306 */
CheckIfVolumeValidOrRecyclable(JobControlRecord * jcr,MediaDbRecord * mr,const char ** reason)307 void CheckIfVolumeValidOrRecyclable(JobControlRecord *jcr, MediaDbRecord *mr, const char **reason)
308 {
309 int ok;
310
311 *reason = NULL;
312
313 /*
314 * Check if a duration or limit has expired
315 */
316 if (bstrcmp(mr->VolStatus, "Append") && HasVolumeExpired(jcr, mr)) {
317 *reason = _("volume has expired");
318 /*
319 * Keep going because we may be able to recycle volume
320 */
321 }
322
323 /*
324 * Now see if we can use the volume as is
325 */
326 if (bstrcmp(mr->VolStatus, "Append") ||
327 bstrcmp(mr->VolStatus, "Recycle")) {
328 *reason = NULL;
329 return;
330 }
331
332 /*
333 * Check if the Volume is already marked for recycling
334 */
335 if (bstrcmp(mr->VolStatus, "Purged")) {
336 if (RecycleVolume(jcr, mr)) {
337 Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
338 *reason = NULL;
339 return;
340 } else {
341 /*
342 * In principle this shouldn't happen
343 */
344 *reason = _("and recycling of current volume failed");
345 return;
346 }
347 }
348
349 /*
350 * At this point, the volume is not valid for writing
351 */
352 *reason = _("but should be Append, Purged or Recycle");
353
354 /*
355 * What we're trying to do here is see if the current volume is
356 * "recyclable" - ie. if we prune all expired jobs off it, is
357 * it now possible to reuse it for the job that it is currently
358 * needed for?
359 */
360 if (!mr->Recycle) {
361 *reason = _("volume has recycling disabled");
362 return;
363 }
364
365 /*
366 * Check retention period from last written, but recycle to within a minute to try to catch close calls ...
367 */
368 if ((mr->LastWritten + mr->VolRetention - 60) < (utime_t)time(NULL) &&
369 jcr->res.pool->recycle_current_volume &&
370 (bstrcmp(mr->VolStatus, "Full") || bstrcmp(mr->VolStatus, "Used"))) {
371 /*
372 * Attempt prune of current volume to see if we can recycle it for use.
373 */
374 UaContext *ua;
375
376 ua = new_ua_context(jcr);
377 ok = PruneVolume(ua, mr);
378 FreeUaContext(ua);
379
380 if (ok) {
381 /*
382 * If fully purged, recycle current volume
383 */
384 if (RecycleVolume(jcr, mr)) {
385 Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
386 *reason = NULL;
387 } else {
388 *reason = _("but should be Append, Purged or Recycle (recycling of the "
389 "current volume failed)");
390 }
391 } else {
392 *reason = _("but should be Append, Purged or Recycle (cannot automatically "
393 "recycle current volume, as it still contains unpruned data "
394 "or the Volume Retention time has not expired.)");
395 }
396 }
397 }
398
399 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
400
GetScratchVolume(JobControlRecord * jcr,bool InChanger,MediaDbRecord * mr,StorageResource * store)401 bool GetScratchVolume(JobControlRecord *jcr, bool InChanger, MediaDbRecord *mr, StorageResource *store)
402 {
403 MediaDbRecord smr; /* for searching scratch pool */
404 PoolDbRecord spr, pr;
405 bool ok = false;
406 bool found = false;
407
408 /*
409 * Only one thread at a time can pull from the scratch pool
410 */
411 P(mutex);
412
413 /*
414 * Get Pool record for Scratch Pool
415 * choose between ScratchPoolId and Scratch
416 * GetPoolRecord will first try ScratchPoolId,
417 * and then try the pool named Scratch
418 */
419 memset(&smr, 0, sizeof(smr));
420 memset(&spr, 0, sizeof(spr));
421
422 bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
423 spr.PoolId = mr->ScratchPoolId;
424 if (jcr->db->GetPoolRecord(jcr, &spr)) {
425 smr.PoolId = spr.PoolId;
426 if (InChanger) {
427 smr.StorageId = mr->StorageId; /* want only Scratch Volumes in changer */
428 }
429
430 bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus)); /* want only appendable volumes */
431 bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
432
433 /*
434 * If we do not find a valid Scratch volume, try recycling any existing purged volumes,
435 * then try to take the oldest volume.
436 */
437 SetStorageidInMr(store, &smr); /* put StorageId in new record */
438 if (jcr->db->FindNextVolume(jcr, 1, InChanger, &smr, NULL)) {
439 found = true;
440 } else if (FindRecycledVolume(jcr, InChanger, &smr, store, NULL)) {
441 found = true;
442 } else if (RecycleOldestPurgedVolume(jcr, InChanger, &smr, store, NULL)) {
443 found = true;
444 }
445
446 if (found) {
447 PoolMem query(PM_MESSAGE);
448
449 /*
450 * Get pool record where the Scratch Volume will go to ensure that we can add a Volume.
451 */
452 memset(&pr, 0, sizeof(pr));
453 bstrncpy(pr.Name, jcr->res.pool->name(), sizeof(pr.Name));
454
455 if (!jcr->db->GetPoolRecord(jcr, &pr)) {
456 Jmsg(jcr, M_WARNING, 0, _("Unable to get Pool record: ERR=%s"), jcr->db->strerror());
457 goto bail_out;
458 }
459
460 /*
461 * Make sure there is room for another volume
462 */
463 if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
464 Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
465 jcr->res.pool->name(), pr.MaxVols);
466 goto bail_out;
467 }
468
469 memcpy(mr, &smr, sizeof(MediaDbRecord));
470 SetStorageidInMr(store, mr);
471
472 /*
473 * Set default parameters from current pool
474 */
475 SetPoolDbrDefaultsInMediaDbr(mr, &pr);
476
477 /*
478 * SetPoolDbrDefaultsInMediaDbr set VolStatus to Append, we could have Recycled media,
479 * also, we retain the old RecyclePoolId.
480 */
481 bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
482 mr->RecyclePoolId = smr.RecyclePoolId;
483
484 if (!jcr->db->UpdateMediaRecord(jcr, mr)) {
485 Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"), jcr->db->strerror());
486 goto bail_out;
487 }
488
489 Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from 'Scratch' pool.\n"), mr->VolumeName);
490
491 ok = true;
492 }
493 }
494
495 bail_out:
496 V(mutex);
497 return ok;
498 }
499 } /* namespace directordaemon */
500