1
2
3
4
19
20 package fsquota
21
22 import (
23 "bufio"
24 "fmt"
25 "os"
26 "path/filepath"
27 "sync"
28
29 "k8s.io/klog/v2"
30 "k8s.io/mount-utils"
31
32 "k8s.io/apimachinery/pkg/api/resource"
33 "k8s.io/apimachinery/pkg/types"
34 "k8s.io/apimachinery/pkg/util/uuid"
35 "k8s.io/kubernetes/pkg/volume/util/fsquota/common"
36 )
37
38
39 var podUidMap = make(map[types.UID]types.UID)
40
41
42 var podQuotaMap = make(map[types.UID]common.QuotaID)
43
44
45 var dirQuotaMap = make(map[string]common.QuotaID)
46
47
48 var quotaPodMap = make(map[common.QuotaID]types.UID)
49
50
51 var dirPodMap = make(map[string]types.UID)
52
53
54
55 var devApplierMap = make(map[string]common.LinuxVolumeQuotaApplier)
56
57
58 var dirApplierMap = make(map[string]common.LinuxVolumeQuotaApplier)
59 var dirApplierLock sync.RWMutex
60
61
62 var podDirCountMap = make(map[types.UID]int)
63
64
65 var quotaSizeMap = make(map[common.QuotaID]int64)
66 var quotaLock sync.RWMutex
67
68 var supportsQuotasMap = make(map[string]bool)
69 var supportsQuotasLock sync.RWMutex
70
71
72 var backingDevMap = make(map[string]string)
73 var backingDevLock sync.RWMutex
74
75 var mountpointMap = make(map[string]string)
76 var mountpointLock sync.RWMutex
77
78 var providers = []common.LinuxVolumeQuotaProvider{
79 &common.VolumeProvider{},
80 }
81
82
83 func detectBackingDevInternal(mountpoint string, mounts string) (string, error) {
84 file, err := os.Open(mounts)
85 if err != nil {
86 return "", err
87 }
88 defer file.Close()
89 scanner := bufio.NewScanner(file)
90 for scanner.Scan() {
91 match := common.MountParseRegexp.FindStringSubmatch(scanner.Text())
92 if match != nil {
93 device := match[1]
94 mount := match[2]
95 if mount == mountpoint {
96 return device, nil
97 }
98 }
99 }
100 return "", fmt.Errorf("couldn't find backing device for %s", mountpoint)
101 }
102
103
104 func detectBackingDev(_ mount.Interface, mountpoint string) (string, error) {
105 return detectBackingDevInternal(mountpoint, common.MountsFile)
106 }
107
108 func clearBackingDev(path string) {
109 backingDevLock.Lock()
110 defer backingDevLock.Unlock()
111 delete(backingDevMap, path)
112 }
113
114
115
116 func detectMountpointInternal(m mount.Interface, path string) (string, error) {
117 for path != "" && path != "/" {
118
119
120
121
122
123
124
125
126
127
128 isNotMount, err := m.IsLikelyNotMountPoint(path)
129 if err != nil {
130 return "/", err
131 }
132 if !isNotMount {
133 return path, nil
134 }
135 path = filepath.Dir(path)
136 }
137 return "/", nil
138 }
139
140 func detectMountpoint(m mount.Interface, path string) (string, error) {
141 xpath, err := filepath.Abs(path)
142 if err != nil {
143 return "/", err
144 }
145 xpath, err = filepath.EvalSymlinks(xpath)
146 if err != nil {
147 return "/", err
148 }
149 if xpath, err = detectMountpointInternal(m, xpath); err == nil {
150 return xpath, nil
151 }
152 return "/", err
153 }
154
155 func clearMountpoint(path string) {
156 mountpointLock.Lock()
157 defer mountpointLock.Unlock()
158 delete(mountpointMap, path)
159 }
160
161
162
163
164 func getFSInfo(m mount.Interface, path string) (string, string, error) {
165 mountpointLock.Lock()
166 defer mountpointLock.Unlock()
167
168 backingDevLock.Lock()
169 defer backingDevLock.Unlock()
170
171 var err error
172
173 mountpoint, okMountpoint := mountpointMap[path]
174 if !okMountpoint {
175 mountpoint, err = detectMountpoint(m, path)
176 if err != nil {
177 return "", "", fmt.Errorf("cannot determine mountpoint for %s: %v", path, err)
178 }
179 }
180
181 backingDev, okBackingDev := backingDevMap[path]
182 if !okBackingDev {
183 backingDev, err = detectBackingDev(m, mountpoint)
184 if err != nil {
185 return "", "", fmt.Errorf("cannot determine backing device for %s: %v", path, err)
186 }
187 }
188 mountpointMap[path] = mountpoint
189 backingDevMap[path] = backingDev
190 return mountpoint, backingDev, nil
191 }
192
193 func clearFSInfo(path string) {
194 clearMountpoint(path)
195 clearBackingDev(path)
196 }
197
198 func getApplier(path string) common.LinuxVolumeQuotaApplier {
199 dirApplierLock.Lock()
200 defer dirApplierLock.Unlock()
201 return dirApplierMap[path]
202 }
203
204 func setApplier(path string, applier common.LinuxVolumeQuotaApplier) {
205 dirApplierLock.Lock()
206 defer dirApplierLock.Unlock()
207 dirApplierMap[path] = applier
208 }
209
210 func clearApplier(path string) {
211 dirApplierLock.Lock()
212 defer dirApplierLock.Unlock()
213 delete(dirApplierMap, path)
214 }
215
216 func setQuotaOnDir(path string, id common.QuotaID, bytes int64) error {
217 return getApplier(path).SetQuotaOnDir(path, id, bytes)
218 }
219
220 func GetQuotaOnDir(m mount.Interface, path string) (common.QuotaID, error) {
221 _, _, err := getFSInfo(m, path)
222 if err != nil {
223 return common.BadQuotaID, err
224 }
225 return getApplier(path).GetQuotaOnDir(path)
226 }
227
228 func clearQuotaOnDir(m mount.Interface, path string) error {
229
230
231 klog.V(4).Infof("clearQuotaOnDir %s", path)
232 supportsQuotas, err := SupportsQuotas(m, path)
233 if err != nil {
234
235
236 klog.V(3).Infof("Attempt to check for quota support failed: %v", err)
237 }
238 if !supportsQuotas {
239 return nil
240 }
241 projid, err := GetQuotaOnDir(m, path)
242 if err == nil && projid != common.BadQuotaID {
243
244
245 err = setQuotaOnDir(path, projid, 0)
246 if err != nil {
247 klog.V(3).Infof("Attempt to clear quota failed: %v", err)
248 }
249
250
251 err1 := removeProjectID(path, projid)
252 if err1 != nil {
253 klog.V(3).Infof("Attempt to remove quota ID from system files failed: %v", err1)
254 }
255 clearFSInfo(path)
256 if err != nil {
257 return err
258 }
259 return err1
260 }
261
262
263 klog.V(3).Infof("clearQuotaOnDir fails %v", err)
264 return nil
265 }
266
267
268
269
270
271
272 func SupportsQuotas(m mount.Interface, path string) (bool, error) {
273 if !enabledQuotasForMonitoring() {
274 klog.V(3).Info("SupportsQuotas called, but quotas disabled")
275 return false, nil
276 }
277 supportsQuotasLock.Lock()
278 defer supportsQuotasLock.Unlock()
279 if supportsQuotas, ok := supportsQuotasMap[path]; ok {
280 return supportsQuotas, nil
281 }
282 mount, dev, err := getFSInfo(m, path)
283 if err != nil {
284 return false, err
285 }
286
287 applier, ok := devApplierMap[mount]
288 if !ok {
289 for _, provider := range providers {
290 if applier = provider.GetQuotaApplier(mount, dev); applier != nil {
291 devApplierMap[mount] = applier
292 break
293 }
294 }
295 }
296 if applier != nil {
297 supportsQuotasMap[path] = true
298 setApplier(path, applier)
299 return true, nil
300 }
301 delete(backingDevMap, path)
302 delete(mountpointMap, path)
303 return false, nil
304 }
305
306
307
308
309
310 func AssignQuota(m mount.Interface, path string, poduid types.UID, bytes *resource.Quantity) error {
311 if bytes == nil {
312 return fmt.Errorf("attempting to assign null quota to %s", path)
313 }
314 ibytes := bytes.Value()
315 if ok, err := SupportsQuotas(m, path); !ok {
316 return fmt.Errorf("quotas not supported on %s: %v", path, err)
317 }
318 quotaLock.Lock()
319 defer quotaLock.Unlock()
320
321
322
323
324
325
326
327
328
329 externalPodUid := poduid
330 internalPodUid, ok := dirPodMap[path]
331 if ok {
332 if podUidMap[internalPodUid] != externalPodUid {
333 return fmt.Errorf("requesting quota on existing directory %s but different pod %s %s", path, podUidMap[internalPodUid], externalPodUid)
334 }
335 } else {
336 internalPodUid = types.UID(uuid.NewUUID())
337 }
338 oid, ok := podQuotaMap[internalPodUid]
339 if ok {
340 if quotaSizeMap[oid] != ibytes {
341 return fmt.Errorf("requesting quota of different size: old %v new %v", quotaSizeMap[oid], bytes)
342 }
343 if _, ok := dirPodMap[path]; ok {
344 return nil
345 }
346 } else {
347 oid = common.BadQuotaID
348 }
349 id, err := createProjectID(path, oid)
350 if err == nil {
351 if oid != common.BadQuotaID && oid != id {
352 return fmt.Errorf("attempt to reassign quota %v to %v", oid, id)
353 }
354
355
356 fsbytes := ibytes
357 if fsbytes > 0 {
358 fsbytes = -1
359 }
360 if err = setQuotaOnDir(path, id, fsbytes); err == nil {
361 quotaPodMap[id] = internalPodUid
362 quotaSizeMap[id] = ibytes
363 podQuotaMap[internalPodUid] = id
364 dirQuotaMap[path] = id
365 dirPodMap[path] = internalPodUid
366 podUidMap[internalPodUid] = externalPodUid
367 podDirCountMap[internalPodUid]++
368 klog.V(4).Infof("Assigning quota ID %d (request limit %d, actual limit %d) to %s", id, ibytes, fsbytes, path)
369 return nil
370 }
371 removeProjectID(path, id)
372 }
373 return fmt.Errorf("assign quota FAILED %v", err)
374 }
375
376
377 func GetConsumption(path string) (*resource.Quantity, error) {
378
379
380 quotaLock.Lock()
381 defer quotaLock.Unlock()
382 applier := getApplier(path)
383
384 if applier == nil {
385 return nil, nil
386 }
387 ibytes, err := applier.GetConsumption(path, dirQuotaMap[path])
388 if err != nil {
389 return nil, err
390 }
391 return resource.NewQuantity(ibytes, resource.DecimalSI), nil
392 }
393
394
395 func GetInodes(path string) (*resource.Quantity, error) {
396
397
398 quotaLock.Lock()
399 defer quotaLock.Unlock()
400 applier := getApplier(path)
401
402 if applier == nil {
403 return nil, nil
404 }
405 inodes, err := applier.GetInodes(path, dirQuotaMap[path])
406 if err != nil {
407 return nil, err
408 }
409 return resource.NewQuantity(inodes, resource.DecimalSI), nil
410 }
411
412
413 func ClearQuota(m mount.Interface, path string) error {
414 klog.V(3).Infof("ClearQuota %s", path)
415 if !enabledQuotasForMonitoring() {
416 return fmt.Errorf("clearQuota called, but quotas disabled")
417 }
418 quotaLock.Lock()
419 defer quotaLock.Unlock()
420 poduid, ok := dirPodMap[path]
421 if !ok {
422
423
424
425
426
427 defer delete(supportsQuotasMap, path)
428 defer clearApplier(path)
429 return clearQuotaOnDir(m, path)
430 }
431 _, ok = podQuotaMap[poduid]
432 if !ok {
433 return fmt.Errorf("clearQuota: No quota available for %s", path)
434 }
435 projid, err := GetQuotaOnDir(m, path)
436 if err != nil {
437
438
439 klog.V(3).Infof("Attempt to check quota ID %v on dir %s failed: %v", dirQuotaMap[path], path, err)
440 }
441 if projid != dirQuotaMap[path] {
442 return fmt.Errorf("expected quota ID %v on dir %s does not match actual %v", dirQuotaMap[path], path, projid)
443 }
444 count, ok := podDirCountMap[poduid]
445 if count <= 1 || !ok {
446 err = clearQuotaOnDir(m, path)
447
448
449 if err != nil {
450 klog.V(3).Infof("Unable to clear quota %v %s: %v", dirQuotaMap[path], path, err)
451 }
452 delete(quotaSizeMap, podQuotaMap[poduid])
453 delete(quotaPodMap, podQuotaMap[poduid])
454 delete(podDirCountMap, poduid)
455 delete(podQuotaMap, poduid)
456 delete(podUidMap, poduid)
457 } else {
458 err = removeProjectID(path, projid)
459 podDirCountMap[poduid]--
460 klog.V(4).Infof("Not clearing quota for pod %s; still %v dirs outstanding", poduid, podDirCountMap[poduid])
461 }
462 delete(dirPodMap, path)
463 delete(dirQuotaMap, path)
464 delete(supportsQuotasMap, path)
465 clearApplier(path)
466 if err != nil {
467 return fmt.Errorf("unable to clear quota for %s: %v", path, err)
468 }
469 return nil
470 }
471
View as plain text