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