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