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