-
Notifications
You must be signed in to change notification settings - Fork 0
/
local_fsgroup_quota.go
151 lines (129 loc) · 4.85 KB
/
local_fsgroup_quota.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package localquota
import (
"bytes"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
g "github.com/onsi/ginkgo"
o "github.com/onsi/gomega"
"github.com/openshift/origin/pkg/volume/emptydir"
exutil "github.com/openshift/origin/test/extended/util"
)
const (
volDirEnvVar = "VOLUME_DIR"
podCreationTimeout = 120 // seconds
expectedQuotaKb = 917504 // launcher script sets 896Mi, xfs_quota reports in Kb.
)
func lookupFSGroup(oc *exutil.CLI, project string) (int, error) {
gidRange, err := oc.Run("get").Args("project", project,
"--template='{{ index .metadata.annotations \"openshift.io/sa.scc.supplemental-groups\" }}'").Output()
if err != nil {
return 0, err
}
// gidRange will be something like: 1000030000/10000
fsGroupStr := strings.Split(gidRange, "/")[0]
fsGroupStr = strings.Replace(fsGroupStr, "'", "", -1)
fsGroup, err := strconv.Atoi(fsGroupStr)
if err != nil {
return 0, err
}
return fsGroup, nil
}
// lookupXFSQuota runs an xfs_quota report and parses the output
// looking for the given fsGroup ID's hard quota.
//
// Will return -1 if no quota was found for the fsGroup, and return
// an error if something legitimately goes wrong in parsing the output.
//
// Output from this command looks like:
//
// $ xfs_quota -x -c 'report -n -L 1000030000 -U 1000030000' /tmp/openshift/xfs-vol-dir
// Group quota on /tmp/openshift/xfs-vol-dir (/dev/sdb2)
// Blocks
// Group ID Used Soft Hard Warn/Grace
// ---------- --------------------------------------------------
// #1000030000 0 524288 524288 00 [--------]
func lookupXFSQuota(oc *exutil.CLI, fsGroup int, volDir string) (int, error) {
// First lookup the filesystem device the volumeDir resides on:
fsDevice, err := emptydir.GetFSDevice(volDir)
if err != nil {
return 0, err
}
args := []string{"xfs_quota", "-x", "-c", fmt.Sprintf("report -n -L %d -U %d", fsGroup, fsGroup), fsDevice}
cmd := exec.Command("sudo", args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
outBytes, reportErr := cmd.Output()
if reportErr != nil {
return 0, reportErr
}
quotaReport := string(outBytes)
// Parse output looking for lines starting with a #, which are the lines with
// group IDs and their quotas:
lines := strings.Split(quotaReport, "\n")
for _, l := range lines {
if strings.HasPrefix(l, fmt.Sprintf("#%d", fsGroup)) {
words := strings.Fields(l)
if len(words) != 6 {
return 0, fmt.Errorf("expected 6 words in quota line: %s", l)
}
quota, err := strconv.Atoi(words[3])
if err != nil {
return 0, err
}
return quota, nil
}
}
// We repeat this check until the quota shows up or we time out, so not
// being able to find the GID in the output does not imply an error, just
// that we haven't found it yet.
return -1, nil
}
// waitForQuotaToBeApplied will check for the expected quota, and wait a short interval if
// not found until we reach the timeout. If we were unable to find the quota we expected,
// an error will be returned. If we found the expected quota in time we will return nil.
func waitForQuotaToBeApplied(oc *exutil.CLI, fsGroup int, volDir string) error {
secondsWaited := 0
for secondsWaited < podCreationTimeout {
quotaFound, quotaErr := lookupXFSQuota(oc, fsGroup, volDir)
o.Expect(quotaErr).NotTo(o.HaveOccurred())
if quotaFound == expectedQuotaKb {
return nil
}
time.Sleep(1 * time.Second)
secondsWaited = secondsWaited + 1
}
return fmt.Errorf("expected quota was not applied in time")
}
var _ = g.Describe("[volumes] Test local storage quota", func() {
defer g.GinkgoRecover()
var (
oc = exutil.NewCLI("local-quota", exutil.KubeConfigPath())
emptyDirPodFixture = exutil.FixturePath("..", "..", "examples", "hello-openshift", "hello-pod.json")
)
g.Describe("FSGroup local storage quota", func() {
g.It("should be applied to XFS filesystem when a pod is created", func() {
oc.SetOutputDir(exutil.TestContext.OutputDir)
project := oc.Namespace()
// Verify volDir is on XFS, if not this test can't pass:
volDir := os.Getenv(volDirEnvVar)
g.By(fmt.Sprintf("make sure volume directory (%s) is on an XFS filesystem", volDir))
o.Expect(volDir).NotTo(o.Equal(""))
args := []string{"-f", "-c", "'%T'", volDir}
outBytes, _ := exec.Command("stat", args...).Output()
o.Expect(strings.Contains(string(outBytes), "xfs")).To(o.BeTrue())
g.By("lookup test projects fsGroup ID")
fsGroup, err := lookupFSGroup(oc, project)
o.Expect(err).NotTo(o.HaveOccurred())
g.By("create hello-openshift pod with emptyDir volume")
_, createPodErr := oc.Run("create").Args("-f", emptyDirPodFixture).Output()
o.Expect(createPodErr).NotTo(o.HaveOccurred())
g.By("wait for XFS quota to be applied and verify")
lookupQuotaErr := waitForQuotaToBeApplied(oc, fsGroup, volDir)
o.Expect(lookupQuotaErr).NotTo(o.HaveOccurred())
})
})
})