Browse code

projectquota: sync next projectID across Control instances

Signed-off-by: Timo Rothenpieler <timo@rothenpieler.org>

Timo Rothenpieler authored on 2020/08/10 04:52:20
Showing 2 changed files
... ...
@@ -55,6 +55,7 @@ import (
55 55
 	"io/ioutil"
56 56
 	"path"
57 57
 	"path/filepath"
58
+	"sync"
58 59
 	"unsafe"
59 60
 
60 61
 	"github.com/containerd/containerd/sys"
... ...
@@ -63,6 +64,33 @@ import (
63 63
 	"golang.org/x/sys/unix"
64 64
 )
65 65
 
66
+type pquotaState struct {
67
+	sync.Mutex
68
+	nextProjectID uint32
69
+}
70
+
71
+var pquotaStateInst *pquotaState
72
+var pquotaStateOnce sync.Once
73
+
74
+// getPquotaState - get global pquota state tracker instance
75
+func getPquotaState() *pquotaState {
76
+	pquotaStateOnce.Do(func() {
77
+		pquotaStateInst = &pquotaState{
78
+			nextProjectID: 1,
79
+		}
80
+	})
81
+	return pquotaStateInst
82
+}
83
+
84
+// registerBasePath - register a new base path and update nextProjectID
85
+func (state *pquotaState) updateMinProjID(minProjectID uint32) {
86
+	state.Lock()
87
+	defer state.Unlock()
88
+	if state.nextProjectID <= minProjectID {
89
+		state.nextProjectID = minProjectID + 1
90
+	}
91
+}
92
+
66 93
 // NewControl - initialize project quota support.
67 94
 // Test to make sure that quota can be set on a test dir and find
68 95
 // the first project id to be used for the next container create.
... ...
@@ -115,11 +143,11 @@ func NewControl(basePath string) (*Control, error) {
115 115
 	//
116 116
 	// Get project id of parent dir as minimal id to be used by driver
117 117
 	//
118
-	minProjectID, err := getProjectID(basePath)
118
+	baseProjectID, err := getProjectID(basePath)
119 119
 	if err != nil {
120 120
 		return nil, err
121 121
 	}
122
-	minProjectID++
122
+	minProjectID := baseProjectID + 1
123 123
 
124 124
 	//
125 125
 	// Test if filesystem supports project quotas by trying to set
... ...
@@ -134,19 +162,24 @@ func NewControl(basePath string) (*Control, error) {
134 134
 
135 135
 	q := Control{
136 136
 		backingFsBlockDev: backingFsBlockDev,
137
-		nextProjectID:     minProjectID + 1,
138 137
 		quotas:            make(map[string]uint32),
139 138
 	}
140 139
 
141 140
 	//
141
+	// update minimum project ID
142
+	//
143
+	state := getPquotaState()
144
+	state.updateMinProjID(minProjectID)
145
+
146
+	//
142 147
 	// get first project id to be used for next container
143 148
 	//
144
-	err = q.findNextProjectID(basePath)
149
+	err = q.findNextProjectID(basePath, baseProjectID)
145 150
 	if err != nil {
146 151
 		return nil, err
147 152
 	}
148 153
 
149
-	logrus.Debugf("NewControl(%s): nextProjectID = %d", basePath, q.nextProjectID)
154
+	logrus.Debugf("NewControl(%s): nextProjectID = %d", basePath, state.nextProjectID)
150 155
 	return &q, nil
151 156
 }
152 157
 
... ...
@@ -157,19 +190,24 @@ func (q *Control) SetQuota(targetPath string, quota Quota) error {
157 157
 	projectID, ok := q.quotas[targetPath]
158 158
 	q.RUnlock()
159 159
 	if !ok {
160
-		q.Lock()
161
-		projectID = q.nextProjectID
160
+		state := getPquotaState()
161
+		state.Lock()
162
+		projectID = state.nextProjectID
162 163
 
163 164
 		//
164 165
 		// assign project id to new container directory
165 166
 		//
166 167
 		err := setProjectID(targetPath, projectID)
167 168
 		if err != nil {
168
-			q.Unlock()
169
+			state.Unlock()
169 170
 			return err
170 171
 		}
172
+
173
+		state.nextProjectID++
174
+		state.Unlock()
175
+
176
+		q.Lock()
171 177
 		q.quotas[targetPath] = projectID
172
-		q.nextProjectID++
173 178
 		q.Unlock()
174 179
 	}
175 180
 
... ...
@@ -279,9 +317,25 @@ func setProjectID(targetPath string, projectID uint32) error {
279 279
 
280 280
 // findNextProjectID - find the next project id to be used for containers
281 281
 // by scanning driver home directory to find used project ids
282
-func (q *Control) findNextProjectID(home string) error {
283
-	q.Lock()
284
-	defer q.Unlock()
282
+func (q *Control) findNextProjectID(home string, baseID uint32) error {
283
+	state := getPquotaState()
284
+	state.Lock()
285
+	defer state.Unlock()
286
+
287
+	checkProjID := func(path string) (uint32, error) {
288
+		projid, err := getProjectID(path)
289
+		if err != nil {
290
+			return projid, err
291
+		}
292
+		if projid > 0 {
293
+			q.quotas[path] = projid
294
+		}
295
+		if state.nextProjectID <= projid {
296
+			state.nextProjectID = projid + 1
297
+		}
298
+		return projid, nil
299
+	}
300
+
285 301
 	files, err := ioutil.ReadDir(home)
286 302
 	if err != nil {
287 303
 		return errors.Errorf("read directory failed: %s", home)
... ...
@@ -291,15 +345,26 @@ func (q *Control) findNextProjectID(home string) error {
291 291
 			continue
292 292
 		}
293 293
 		path := filepath.Join(home, file.Name())
294
-		projid, err := getProjectID(path)
294
+		projid, err := checkProjID(path)
295 295
 		if err != nil {
296 296
 			return err
297 297
 		}
298
-		if projid > 0 {
299
-			q.quotas[path] = projid
298
+		if projid > 0 && projid != baseID {
299
+			continue
300
+		}
301
+		subfiles, err := ioutil.ReadDir(path)
302
+		if err != nil {
303
+			return errors.Errorf("read directory failed: %s", path)
300 304
 		}
301
-		if q.nextProjectID <= projid {
302
-			q.nextProjectID = projid + 1
305
+		for _, subfile := range subfiles {
306
+			if !subfile.IsDir() {
307
+				continue
308
+			}
309
+			subpath := filepath.Join(path, subfile.Name())
310
+			_, err := checkProjID(subpath)
311
+			if err != nil {
312
+				return err
313
+			}
303 314
 		}
304 315
 	}
305 316
 
... ...
@@ -14,6 +14,5 @@ type Quota struct {
14 14
 type Control struct {
15 15
 	backingFsBlockDev string
16 16
 	sync.RWMutex      // protect nextProjectID and quotas map
17
-	nextProjectID     uint32
18 17
 	quotas            map[string]uint32
19 18
 }