quota_linux.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // +build linux
  2. /*
  3. Copyright 2018 The Kubernetes Authors.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package fsquota
  15. import (
  16. "bufio"
  17. "fmt"
  18. "os"
  19. "path/filepath"
  20. "sync"
  21. "k8s.io/klog"
  22. "k8s.io/utils/mount"
  23. "k8s.io/apimachinery/pkg/api/resource"
  24. "k8s.io/apimachinery/pkg/types"
  25. "k8s.io/apimachinery/pkg/util/uuid"
  26. "k8s.io/kubernetes/pkg/volume/util/fsquota/common"
  27. )
  28. // Pod -> ID
  29. var podQuotaMap = make(map[types.UID]common.QuotaID)
  30. // Dir -> ID (for convenience)
  31. var dirQuotaMap = make(map[string]common.QuotaID)
  32. // ID -> pod
  33. var quotaPodMap = make(map[common.QuotaID]types.UID)
  34. // Directory -> pod
  35. var dirPodMap = make(map[string]types.UID)
  36. // Backing device -> applier
  37. // This is *not* cleaned up; its size will be bounded.
  38. var devApplierMap = make(map[string]common.LinuxVolumeQuotaApplier)
  39. // Directory -> applier
  40. var dirApplierMap = make(map[string]common.LinuxVolumeQuotaApplier)
  41. var dirApplierLock sync.RWMutex
  42. // Pod -> refcount
  43. var podDirCountMap = make(map[types.UID]int)
  44. // ID -> size
  45. var quotaSizeMap = make(map[common.QuotaID]int64)
  46. var quotaLock sync.RWMutex
  47. var supportsQuotasMap = make(map[string]bool)
  48. var supportsQuotasLock sync.RWMutex
  49. // Directory -> backingDev
  50. var backingDevMap = make(map[string]string)
  51. var backingDevLock sync.RWMutex
  52. var mountpointMap = make(map[string]string)
  53. var mountpointLock sync.RWMutex
  54. var providers = []common.LinuxVolumeQuotaProvider{
  55. &common.VolumeProvider{},
  56. }
  57. // Separate the innards for ease of testing
  58. func detectBackingDevInternal(mountpoint string, mounts string) (string, error) {
  59. file, err := os.Open(mounts)
  60. if err != nil {
  61. return "", err
  62. }
  63. defer file.Close()
  64. scanner := bufio.NewScanner(file)
  65. for scanner.Scan() {
  66. match := common.MountParseRegexp.FindStringSubmatch(scanner.Text())
  67. if match != nil {
  68. device := match[1]
  69. mount := match[2]
  70. if mount == mountpoint {
  71. return device, nil
  72. }
  73. }
  74. }
  75. return "", fmt.Errorf("couldn't find backing device for %s", mountpoint)
  76. }
  77. // detectBackingDev assumes that the mount point provided is valid
  78. func detectBackingDev(_ mount.Interface, mountpoint string) (string, error) {
  79. return detectBackingDevInternal(mountpoint, common.MountsFile)
  80. }
  81. func clearBackingDev(path string) {
  82. backingDevLock.Lock()
  83. defer backingDevLock.Unlock()
  84. delete(backingDevMap, path)
  85. }
  86. // Assumes that the path has been fully canonicalized
  87. // Breaking this up helps with testing
  88. func detectMountpointInternal(m mount.Interface, path string) (string, error) {
  89. for path != "" && path != "/" {
  90. // per k8s.io/utils/mount/mount_linux this detects all but
  91. // a bind mount from one part of a mount to another.
  92. // For our purposes that's fine; we simply want the "true"
  93. // mount point
  94. //
  95. // IsNotMountPoint proved much more troublesome; it actually
  96. // scans the mounts, and when a lot of mount/unmount
  97. // activity takes place, it is not able to get a consistent
  98. // view of /proc/self/mounts, causing it to time out and
  99. // report incorrectly.
  100. isNotMount, err := m.IsLikelyNotMountPoint(path)
  101. if err != nil {
  102. return "/", err
  103. }
  104. if !isNotMount {
  105. return path, nil
  106. }
  107. path = filepath.Dir(path)
  108. }
  109. return "/", nil
  110. }
  111. func detectMountpoint(m mount.Interface, path string) (string, error) {
  112. xpath, err := filepath.Abs(path)
  113. if err != nil {
  114. return "/", err
  115. }
  116. xpath, err = filepath.EvalSymlinks(xpath)
  117. if err != nil {
  118. return "/", err
  119. }
  120. if xpath, err = detectMountpointInternal(m, xpath); err == nil {
  121. return xpath, nil
  122. }
  123. return "/", err
  124. }
  125. func clearMountpoint(path string) {
  126. mountpointLock.Lock()
  127. defer mountpointLock.Unlock()
  128. delete(mountpointMap, path)
  129. }
  130. // getFSInfo Returns mountpoint and backing device
  131. // getFSInfo should cache the mountpoint and backing device for the
  132. // path.
  133. func getFSInfo(m mount.Interface, path string) (string, string, error) {
  134. mountpointLock.Lock()
  135. defer mountpointLock.Unlock()
  136. backingDevLock.Lock()
  137. defer backingDevLock.Unlock()
  138. var err error
  139. mountpoint, okMountpoint := mountpointMap[path]
  140. if !okMountpoint {
  141. mountpoint, err = detectMountpoint(m, path)
  142. if err != nil {
  143. return "", "", fmt.Errorf("Cannot determine mountpoint for %s: %v", path, err)
  144. }
  145. }
  146. backingDev, okBackingDev := backingDevMap[path]
  147. if !okBackingDev {
  148. backingDev, err = detectBackingDev(m, mountpoint)
  149. if err != nil {
  150. return "", "", fmt.Errorf("Cannot determine backing device for %s: %v", path, err)
  151. }
  152. }
  153. mountpointMap[path] = mountpoint
  154. backingDevMap[path] = backingDev
  155. return mountpoint, backingDev, nil
  156. }
  157. func clearFSInfo(path string) {
  158. clearMountpoint(path)
  159. clearBackingDev(path)
  160. }
  161. func getApplier(path string) common.LinuxVolumeQuotaApplier {
  162. dirApplierLock.Lock()
  163. defer dirApplierLock.Unlock()
  164. return dirApplierMap[path]
  165. }
  166. func setApplier(path string, applier common.LinuxVolumeQuotaApplier) {
  167. dirApplierLock.Lock()
  168. defer dirApplierLock.Unlock()
  169. dirApplierMap[path] = applier
  170. }
  171. func clearApplier(path string) {
  172. dirApplierLock.Lock()
  173. defer dirApplierLock.Unlock()
  174. delete(dirApplierMap, path)
  175. }
  176. func setQuotaOnDir(path string, id common.QuotaID, bytes int64) error {
  177. return getApplier(path).SetQuotaOnDir(path, id, bytes)
  178. }
  179. func getQuotaOnDir(m mount.Interface, path string) (common.QuotaID, error) {
  180. _, _, err := getFSInfo(m, path)
  181. if err != nil {
  182. return common.BadQuotaID, err
  183. }
  184. return getApplier(path).GetQuotaOnDir(path)
  185. }
  186. func clearQuotaOnDir(m mount.Interface, path string) error {
  187. // Since we may be called without path being in the map,
  188. // we explicitly have to check in this case.
  189. klog.V(4).Infof("clearQuotaOnDir %s", path)
  190. supportsQuotas, err := SupportsQuotas(m, path)
  191. if err != nil {
  192. // Log-and-continue instead of returning an error for now
  193. // due to unspecified backwards compatibility concerns (a subject to revise)
  194. klog.V(3).Infof("Attempt to check for quota support failed: %v", err)
  195. }
  196. if !supportsQuotas {
  197. return nil
  198. }
  199. projid, err := getQuotaOnDir(m, path)
  200. if err == nil && projid != common.BadQuotaID {
  201. // This means that we have a quota on the directory but
  202. // we can't clear it. That's not good.
  203. err = setQuotaOnDir(path, projid, 0)
  204. if err != nil {
  205. klog.V(3).Infof("Attempt to clear quota failed: %v", err)
  206. }
  207. // Even if clearing the quota failed, we still need to
  208. // try to remove the project ID, or that may be left dangling.
  209. err1 := removeProjectID(path, projid)
  210. if err1 != nil {
  211. klog.V(3).Infof("Attempt to remove quota ID from system files failed: %v", err1)
  212. }
  213. clearFSInfo(path)
  214. if err != nil {
  215. return err
  216. }
  217. return err1
  218. }
  219. // If we couldn't get a quota, that's fine -- there may
  220. // never have been one, and we have no way to know otherwise
  221. klog.V(3).Infof("clearQuotaOnDir fails %v", err)
  222. return nil
  223. }
  224. // SupportsQuotas -- Does the path support quotas
  225. // Cache the applier for paths that support quotas. For paths that don't,
  226. // don't cache the result because nothing will clean it up.
  227. // However, do cache the device->applier map; the number of devices
  228. // is bounded.
  229. func SupportsQuotas(m mount.Interface, path string) (bool, error) {
  230. if !enabledQuotasForMonitoring() {
  231. klog.V(3).Info("SupportsQuotas called, but quotas disabled")
  232. return false, nil
  233. }
  234. supportsQuotasLock.Lock()
  235. defer supportsQuotasLock.Unlock()
  236. if supportsQuotas, ok := supportsQuotasMap[path]; ok {
  237. return supportsQuotas, nil
  238. }
  239. mount, dev, err := getFSInfo(m, path)
  240. if err != nil {
  241. return false, err
  242. }
  243. // Do we know about this device?
  244. applier, ok := devApplierMap[mount]
  245. if !ok {
  246. for _, provider := range providers {
  247. if applier = provider.GetQuotaApplier(mount, dev); applier != nil {
  248. devApplierMap[mount] = applier
  249. break
  250. }
  251. }
  252. }
  253. if applier != nil {
  254. supportsQuotasMap[path] = true
  255. setApplier(path, applier)
  256. return true, nil
  257. }
  258. delete(backingDevMap, path)
  259. delete(mountpointMap, path)
  260. return false, nil
  261. }
  262. // AssignQuota -- assign a quota to the specified directory.
  263. // AssignQuota chooses the quota ID based on the pod UID and path.
  264. // If the pod UID is identical to another one known, it may (but presently
  265. // doesn't) choose the same quota ID as other volumes in the pod.
  266. func AssignQuota(m mount.Interface, path string, poduid types.UID, bytes *resource.Quantity) error {
  267. if bytes == nil {
  268. return fmt.Errorf("Attempting to assign null quota to %s", path)
  269. }
  270. ibytes := bytes.Value()
  271. if ok, err := SupportsQuotas(m, path); !ok {
  272. return fmt.Errorf("Quotas not supported on %s: %v", path, err)
  273. }
  274. quotaLock.Lock()
  275. defer quotaLock.Unlock()
  276. // Current policy is to set individual quotas on each volumes.
  277. // If we decide later that we want to assign one quota for all
  278. // volumes in a pod, we can simply remove this line of code.
  279. // If and when we decide permanently that we're going to adop
  280. // one quota per volume, we can rip all of the pod code out.
  281. poduid = types.UID(uuid.NewUUID())
  282. if pod, ok := dirPodMap[path]; ok && pod != poduid {
  283. return fmt.Errorf("Requesting quota on existing directory %s but different pod %s %s", path, pod, poduid)
  284. }
  285. oid, ok := podQuotaMap[poduid]
  286. if ok {
  287. if quotaSizeMap[oid] != ibytes {
  288. return fmt.Errorf("Requesting quota of different size: old %v new %v", quotaSizeMap[oid], bytes)
  289. }
  290. } else {
  291. oid = common.BadQuotaID
  292. }
  293. id, err := createProjectID(path, oid)
  294. if err == nil {
  295. if oid != common.BadQuotaID && oid != id {
  296. return fmt.Errorf("Attempt to reassign quota %v to %v", oid, id)
  297. }
  298. // When enforcing quotas are enabled, we'll condition this
  299. // on their being disabled also.
  300. if ibytes > 0 {
  301. ibytes = -1
  302. }
  303. if err = setQuotaOnDir(path, id, ibytes); err == nil {
  304. quotaPodMap[id] = poduid
  305. quotaSizeMap[id] = ibytes
  306. podQuotaMap[poduid] = id
  307. dirQuotaMap[path] = id
  308. dirPodMap[path] = poduid
  309. podDirCountMap[poduid]++
  310. klog.V(4).Infof("Assigning quota ID %d (%d) to %s", id, ibytes, path)
  311. return nil
  312. }
  313. removeProjectID(path, id)
  314. }
  315. return fmt.Errorf("Assign quota FAILED %v", err)
  316. }
  317. // GetConsumption -- retrieve the consumption (in bytes) of the directory
  318. func GetConsumption(path string) (*resource.Quantity, error) {
  319. // Note that we actually need to hold the lock at least through
  320. // running the quota command, so it can't get recycled behind our back
  321. quotaLock.Lock()
  322. defer quotaLock.Unlock()
  323. applier := getApplier(path)
  324. // No applier means directory is not under quota management
  325. if applier == nil {
  326. return nil, nil
  327. }
  328. ibytes, err := applier.GetConsumption(path, dirQuotaMap[path])
  329. if err != nil {
  330. return nil, err
  331. }
  332. return resource.NewQuantity(ibytes, resource.DecimalSI), nil
  333. }
  334. // GetInodes -- retrieve the number of inodes in use under the directory
  335. func GetInodes(path string) (*resource.Quantity, error) {
  336. // Note that we actually need to hold the lock at least through
  337. // running the quota command, so it can't get recycled behind our back
  338. quotaLock.Lock()
  339. defer quotaLock.Unlock()
  340. applier := getApplier(path)
  341. // No applier means directory is not under quota management
  342. if applier == nil {
  343. return nil, nil
  344. }
  345. inodes, err := applier.GetInodes(path, dirQuotaMap[path])
  346. if err != nil {
  347. return nil, err
  348. }
  349. return resource.NewQuantity(inodes, resource.DecimalSI), nil
  350. }
  351. // ClearQuota -- remove the quota assigned to a directory
  352. func ClearQuota(m mount.Interface, path string) error {
  353. klog.V(3).Infof("ClearQuota %s", path)
  354. if !enabledQuotasForMonitoring() {
  355. return fmt.Errorf("ClearQuota called, but quotas disabled")
  356. }
  357. quotaLock.Lock()
  358. defer quotaLock.Unlock()
  359. poduid, ok := dirPodMap[path]
  360. if !ok {
  361. // Nothing in the map either means that there was no
  362. // quota to begin with or that we're clearing a
  363. // stale directory, so if we find a quota, just remove it.
  364. // The process of clearing the quota requires that an applier
  365. // be found, which needs to be cleaned up.
  366. defer delete(supportsQuotasMap, path)
  367. defer clearApplier(path)
  368. return clearQuotaOnDir(m, path)
  369. }
  370. _, ok = podQuotaMap[poduid]
  371. if !ok {
  372. return fmt.Errorf("ClearQuota: No quota available for %s", path)
  373. }
  374. projid, err := getQuotaOnDir(m, path)
  375. if err != nil {
  376. // Log-and-continue instead of returning an error for now
  377. // due to unspecified backwards compatibility concerns (a subject to revise)
  378. klog.V(3).Infof("Attempt to check quota ID %v on dir %s failed: %v", dirQuotaMap[path], path, err)
  379. }
  380. if projid != dirQuotaMap[path] {
  381. return fmt.Errorf("Expected quota ID %v on dir %s does not match actual %v", dirQuotaMap[path], path, projid)
  382. }
  383. count, ok := podDirCountMap[poduid]
  384. if count <= 1 || !ok {
  385. err = clearQuotaOnDir(m, path)
  386. // This error should be noted; we still need to clean up
  387. // and otherwise handle in the same way.
  388. if err != nil {
  389. klog.V(3).Infof("Unable to clear quota %v %s: %v", dirQuotaMap[path], path, err)
  390. }
  391. delete(quotaSizeMap, podQuotaMap[poduid])
  392. delete(quotaPodMap, podQuotaMap[poduid])
  393. delete(podDirCountMap, poduid)
  394. delete(podQuotaMap, poduid)
  395. } else {
  396. err = removeProjectID(path, projid)
  397. podDirCountMap[poduid]--
  398. klog.V(4).Infof("Not clearing quota for pod %s; still %v dirs outstanding", poduid, podDirCountMap[poduid])
  399. }
  400. delete(dirPodMap, path)
  401. delete(dirQuotaMap, path)
  402. delete(supportsQuotasMap, path)
  403. clearApplier(path)
  404. if err != nil {
  405. return fmt.Errorf("Unable to clear quota for %s: %v", path, err)
  406. }
  407. return nil
  408. }