quota_linux.go 13 KB

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