1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2016 Planets Communications B.V.
6    Copyright (C) 2013-2019 Bareos GmbH & Co. KG
7 
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    Affero 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 /*
24  * Matthew Ife Matthew.Ife@ukfast.co.uk
25  */
26 /**
27  * @file
28  * Quota processing routines.
29  */
30 
31 #include "include/bareos.h"
32 #include "dird.h"
33 #include "dird/jcr_private.h"
34 
35 namespace directordaemon {
36 
37 #define debuglevel 100
38 /**
39  * This function returns the total number of bytes difference remaining before
40  * going over quota. Returns: unsigned long long containing remaining bytes
41  * before going over quota. 0 if over quota
42  */
FetchRemainingQuotas(JobControlRecord * jcr)43 uint64_t FetchRemainingQuotas(JobControlRecord* jcr)
44 {
45   uint64_t remaining = 0;
46   uint64_t now = (uint64_t)time(NULL);
47 
48   /*
49    * Quotas not being used ?
50    */
51   if (!jcr->impl->HasQuota) { return 0; }
52 
53   Dmsg2(debuglevel, "JobSumTotalBytes for JobId %d is %llu\n", jcr->JobId,
54         jcr->impl->jr.JobSumTotalBytes);
55   Dmsg1(debuglevel, "Fetching remaining quotas for JobId %d\n", jcr->JobId);
56 
57   /*
58    * If strict quotas on and grace exceeded, enforce the softquota
59    */
60   if (jcr->impl->res.client->StrictQuotas &&
61       jcr->impl->res.client->SoftQuota &&
62       jcr->impl->res.client->GraceTime > 0 &&
63       (now - (uint64_t)jcr->impl->res.client->GraceTime) >
64           (uint64_t)jcr->impl->res.client->SoftQuotaGracePeriod &&
65       jcr->impl->res.client->SoftQuotaGracePeriod > 0) {
66     remaining =
67         jcr->impl->res.client->SoftQuota - jcr->impl->jr.JobSumTotalBytes;
68   } else if (!jcr->impl->res.client->StrictQuotas &&
69              jcr->impl->res.client->SoftQuota &&
70              jcr->impl->res.client->GraceTime > 0 &&
71              jcr->impl->res.client->SoftQuotaGracePeriod > 0 &&
72              (now - (uint64_t)jcr->impl->res.client->GraceTime) >
73                  (uint64_t)jcr->impl->res.client->SoftQuotaGracePeriod) {
74     /*
75      * If strict quotas turned off and grace exceeded use the last known limit
76      */
77     if (jcr->impl->res.client->QuotaLimit >
78         jcr->impl->res.client->SoftQuota) {
79       remaining =
80           jcr->impl->res.client->QuotaLimit - jcr->impl->jr.JobSumTotalBytes;
81     } else {
82       remaining =
83           jcr->impl->res.client->SoftQuota - jcr->impl->jr.JobSumTotalBytes;
84     }
85   } else if (jcr->impl->jr.JobSumTotalBytes <
86              jcr->impl->res.client->HardQuota) {
87     /*
88      * If within the hardquota.
89      */
90     remaining =
91         jcr->impl->res.client->HardQuota - jcr->impl->jr.JobSumTotalBytes;
92   } else {
93     /*
94      * If just over quota return 0. This shouldnt happen because quotas
95      * are checked properly prior to this code.
96      */
97     remaining = 0;
98   }
99 
100   Dmsg4(debuglevel,
101         "Quota for %s is %llu. Remainder is %llu, QuotaLimit: %llu\n",
102         jcr->impl->jr.Name, jcr->impl->jr.JobSumTotalBytes, remaining,
103         jcr->impl->res.client->QuotaLimit);
104 
105   return remaining;
106 }
107 
108 /**
109  * This function returns a truth value depending on the state of hard quotas.
110  * The function compares the total jobbytes against the hard quota.
111  * If the value is true, the quota is reached and termination of the job should
112  * occur.
113  *
114  * Returns: true on reaching quota
115  *          false on not reaching quota.
116  */
CheckHardquotas(JobControlRecord * jcr)117 bool CheckHardquotas(JobControlRecord* jcr)
118 {
119   bool retval = false;
120 
121   /*
122    * Do not check if hardquota is not set
123    */
124   if (jcr->impl->res.client->HardQuota == 0) { goto bail_out; }
125 
126   Dmsg1(debuglevel, "Checking hard quotas for JobId %d\n", jcr->JobId);
127   if (!jcr->impl->HasQuota) {
128     if (jcr->impl->res.client->QuotaIncludeFailedJobs) {
129       if (!jcr->db->get_quota_jobbytes(jcr, &jcr->impl->jr,
130                                        jcr->impl->res.client->JobRetention)) {
131         Jmsg(jcr, M_WARNING, 0, _("Error getting Quota value: ERR=%s"),
132              jcr->db->strerror());
133         goto bail_out;
134       }
135     } else {
136       if (!jcr->db->get_quota_jobbytes_nofailed(
137               jcr, &jcr->impl->jr, jcr->impl->res.client->JobRetention)) {
138         Jmsg(jcr, M_WARNING, 0, _("Error getting Quota value: ERR=%s"),
139              jcr->db->strerror());
140         goto bail_out;
141       }
142     }
143     jcr->impl->HasQuota = true;
144   }
145 
146   if (jcr->impl->jr.JobSumTotalBytes > jcr->impl->res.client->HardQuota) {
147     retval = true;
148     goto bail_out;
149   }
150 
151   Dmsg2(debuglevel, "Quota for JobID: %d is %llu\n", jcr->impl->jr.JobId,
152         jcr->impl->jr.JobSumTotalBytes);
153 
154 bail_out:
155   return retval;
156 }
157 
158 /**
159  * This function returns a truth value depending on the state of soft quotas.
160  * The function compares the total jobbytes against the soft quota.
161  *
162  * If the quotas are not strict (the default) it checks the jobbytes value
163  * against the quota limit previously when running in burst mode during the
164  * grace period.
165  *
166  * It checks if we have exceeded our grace time.
167  *
168  * If the value is true, the quota is reached and termination of the job should
169  * occur.
170  *
171  * Returns: true on reaching soft quota
172  *          false on not reaching soft quota.
173  */
CheckSoftquotas(JobControlRecord * jcr)174 bool CheckSoftquotas(JobControlRecord* jcr)
175 {
176   bool retval = false;
177   uint64_t now = (uint64_t)time(NULL);
178 
179   /*
180    * Do not check if the softquota is not set
181    */
182   if (jcr->impl->res.client->SoftQuota == 0) { goto bail_out; }
183 
184   Dmsg1(debuglevel, "Checking soft quotas for JobId %d\n", jcr->JobId);
185   if (!jcr->impl->HasQuota) {
186     if (jcr->impl->res.client->QuotaIncludeFailedJobs) {
187       if (!jcr->db->get_quota_jobbytes(jcr, &jcr->impl->jr,
188                                        jcr->impl->res.client->JobRetention)) {
189         Jmsg(jcr, M_WARNING, 0, _("Error getting Quota value: ERR=%s"),
190              jcr->db->strerror());
191         goto bail_out;
192       }
193       Dmsg0(debuglevel, "Quota Includes Failed Jobs\n");
194     } else {
195       if (!jcr->db->get_quota_jobbytes_nofailed(
196               jcr, &jcr->impl->jr, jcr->impl->res.client->JobRetention)) {
197         Jmsg(jcr, M_WARNING, 0, _("Error getting Quota value: ERR=%s"),
198              jcr->db->strerror());
199         goto bail_out;
200       }
201       Jmsg(jcr, M_INFO, 0, _("Quota does NOT include Failed Jobs\n"));
202     }
203     jcr->impl->HasQuota = true;
204   }
205 
206   Dmsg2(debuglevel, "Quota for %s is %llu\n", jcr->impl->jr.Name,
207         jcr->impl->jr.JobSumTotalBytes);
208   Dmsg2(debuglevel, "QuotaLimit for %s is %llu\n", jcr->impl->jr.Name,
209         jcr->impl->res.client->QuotaLimit);
210   Dmsg2(debuglevel, "HardQuota for %s is %llu\n", jcr->impl->jr.Name,
211         jcr->impl->res.client->HardQuota);
212   Dmsg2(debuglevel, "SoftQuota for %s is %llu\n", jcr->impl->jr.Name,
213         jcr->impl->res.client->SoftQuota);
214   Dmsg2(debuglevel, "SoftQuota Grace Period for %s is %d\n",
215         jcr->impl->jr.Name, jcr->impl->res.client->SoftQuotaGracePeriod);
216   Dmsg2(debuglevel, "SoftQuota Grace Time for %s is %d\n", jcr->impl->jr.Name,
217         jcr->impl->res.client->GraceTime);
218 
219   if ((jcr->impl->jr.JobSumTotalBytes + jcr->impl->SDJobBytes) >
220       jcr->impl->res.client->SoftQuota) {
221     /*
222      * Only warn once about softquotas in the job
223      * Check if gracetime has been set
224      */
225     if (jcr->impl->res.client->GraceTime == 0 &&
226         jcr->impl->res.client->SoftQuotaGracePeriod) {
227       Dmsg1(debuglevel, "UpdateQuotaGracetime: %d\n", now);
228       if (!jcr->db->UpdateQuotaGracetime(jcr, &jcr->impl->jr)) {
229         Jmsg(jcr, M_WARNING, 0, _("Error setting Quota gracetime: ERR=%s"),
230              jcr->db->strerror());
231       } else {
232         Jmsg(jcr, M_ERROR, 0,
233              _("Softquota Exceeded, Grace Period starts now.\n"));
234       }
235       jcr->impl->res.client->GraceTime = now;
236       goto bail_out;
237     } else if (jcr->impl->res.client->SoftQuotaGracePeriod &&
238                (now - (uint64_t)jcr->impl->res.client->GraceTime) <
239                    (uint64_t)jcr->impl->res.client->SoftQuotaGracePeriod) {
240       Jmsg(jcr, M_ERROR, 0,
241            _("Softquota Exceeded, will be enforced after Grace Period "
242              "expires.\n"));
243     } else if (jcr->impl->res.client->SoftQuotaGracePeriod &&
244                (now - (uint64_t)jcr->impl->res.client->GraceTime) >
245                    (uint64_t)jcr->impl->res.client->SoftQuotaGracePeriod) {
246       /*
247        * If gracetime has expired update else check more if not set softlimit
248        * yet then set and bail out.
249        */
250       if (jcr->impl->res.client->QuotaLimit < 1) {
251         if (!jcr->db->UpdateQuotaSoftlimit(jcr, &jcr->impl->jr)) {
252           Jmsg(jcr, M_WARNING, 0, _("Error setting Quota Softlimit: ERR=%s"),
253                jcr->db->strerror());
254         }
255         Jmsg(jcr, M_WARNING, 0,
256              _("Softquota Exceeded and Grace Period expired.\n"));
257         Jmsg(jcr, M_INFO, 0, _("Setting Burst Quota to %d Bytes.\n"),
258              jcr->impl->jr.JobSumTotalBytes);
259         jcr->impl->res.client->QuotaLimit = jcr->impl->jr.JobSumTotalBytes;
260         retval = true;
261         goto bail_out;
262       } else {
263         /*
264          * If gracetime has expired update else check more if not set softlimit
265          * yet then set and bail out.
266          */
267         if (jcr->impl->res.client->QuotaLimit < 1) {
268           if (!jcr->db->UpdateQuotaSoftlimit(jcr, &jcr->impl->jr)) {
269             Jmsg(jcr, M_WARNING, 0, _("Error setting Quota Softlimit: ERR=%s"),
270                  jcr->db->strerror());
271           }
272           Jmsg(jcr, M_WARNING, 0,
273                _("Soft Quota exceeded and Grace Period expired.\n"));
274           Jmsg(jcr, M_INFO, 0, _("Setting Burst Quota to %d Bytes.\n"),
275                jcr->impl->jr.JobSumTotalBytes);
276           jcr->impl->res.client->QuotaLimit = jcr->impl->jr.JobSumTotalBytes;
277           retval = true;
278           goto bail_out;
279         } else {
280           /*
281            * If we use strict quotas enforce the pure soft quota limit.
282            */
283           if (jcr->impl->res.client->StrictQuotas) {
284             if (jcr->impl->jr.JobSumTotalBytes >
285                 jcr->impl->res.client->SoftQuota) {
286               Dmsg0(debuglevel,
287                     "Soft Quota exceeded, enforcing Strict Quota Limit.\n");
288               retval = true;
289               goto bail_out;
290             }
291           } else {
292             if (jcr->impl->jr.JobSumTotalBytes >=
293                 jcr->impl->res.client->QuotaLimit) {
294               /*
295                * If strict quotas turned off use the last known limit
296                */
297               Jmsg(jcr, M_WARNING, 0,
298                    _("Soft Quota exceeded, enforcing Burst Quota Limit.\n"));
299               retval = true;
300               goto bail_out;
301             }
302           }
303         }
304       }
305     }
306   } else if (jcr->impl->res.client->GraceTime != 0) {
307     /*
308      * Reset softquota
309      */
310     ClientDbRecord cr;
311     cr.ClientId = jcr->impl->jr.ClientId;
312     if (!jcr->db->ResetQuotaRecord(jcr, &cr)) {
313       Jmsg(jcr, M_WARNING, 0, _("Error setting Quota gracetime: ERR=%s\n"),
314            jcr->db->strerror());
315     } else {
316       jcr->impl->res.client->GraceTime = 0;
317       Jmsg(jcr, M_INFO, 0, _("Soft Quota reset, Grace Period ends now.\n"));
318     }
319   }
320 
321 bail_out:
322   return retval;
323 }
324 
325 } /* namespace directordaemon */
326