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