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