|
@@ -55,6 +55,7 @@ import (
|
|
"io/ioutil"
|
|
"io/ioutil"
|
|
"path"
|
|
"path"
|
|
"path/filepath"
|
|
"path/filepath"
|
|
|
|
+ "sync"
|
|
"unsafe"
|
|
"unsafe"
|
|
|
|
|
|
"github.com/containerd/containerd/sys"
|
|
"github.com/containerd/containerd/sys"
|
|
@@ -63,6 +64,33 @@ import (
|
|
"golang.org/x/sys/unix"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+type pquotaState struct {
|
|
|
|
+ sync.Mutex
|
|
|
|
+ nextProjectID uint32
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+var pquotaStateInst *pquotaState
|
|
|
|
+var pquotaStateOnce sync.Once
|
|
|
|
+
|
|
|
|
+// getPquotaState - get global pquota state tracker instance
|
|
|
|
+func getPquotaState() *pquotaState {
|
|
|
|
+ pquotaStateOnce.Do(func() {
|
|
|
|
+ pquotaStateInst = &pquotaState{
|
|
|
|
+ nextProjectID: 1,
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ return pquotaStateInst
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// registerBasePath - register a new base path and update nextProjectID
|
|
|
|
+func (state *pquotaState) updateMinProjID(minProjectID uint32) {
|
|
|
|
+ state.Lock()
|
|
|
|
+ defer state.Unlock()
|
|
|
|
+ if state.nextProjectID <= minProjectID {
|
|
|
|
+ state.nextProjectID = minProjectID + 1
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
// NewControl - initialize project quota support.
|
|
// NewControl - initialize project quota support.
|
|
// Test to make sure that quota can be set on a test dir and find
|
|
// Test to make sure that quota can be set on a test dir and find
|
|
// the first project id to be used for the next container create.
|
|
// the first project id to be used for the next container create.
|
|
@@ -115,11 +143,11 @@ func NewControl(basePath string) (*Control, error) {
|
|
//
|
|
//
|
|
// Get project id of parent dir as minimal id to be used by driver
|
|
// Get project id of parent dir as minimal id to be used by driver
|
|
//
|
|
//
|
|
- minProjectID, err := getProjectID(basePath)
|
|
|
|
|
|
+ baseProjectID, err := getProjectID(basePath)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
- minProjectID++
|
|
|
|
|
|
+ minProjectID := baseProjectID + 1
|
|
|
|
|
|
//
|
|
//
|
|
// Test if filesystem supports project quotas by trying to set
|
|
// Test if filesystem supports project quotas by trying to set
|
|
@@ -134,19 +162,24 @@ func NewControl(basePath string) (*Control, error) {
|
|
|
|
|
|
q := Control{
|
|
q := Control{
|
|
backingFsBlockDev: backingFsBlockDev,
|
|
backingFsBlockDev: backingFsBlockDev,
|
|
- nextProjectID: minProjectID + 1,
|
|
|
|
quotas: make(map[string]uint32),
|
|
quotas: make(map[string]uint32),
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ //
|
|
|
|
+ // update minimum project ID
|
|
|
|
+ //
|
|
|
|
+ state := getPquotaState()
|
|
|
|
+ state.updateMinProjID(minProjectID)
|
|
|
|
+
|
|
//
|
|
//
|
|
// get first project id to be used for next container
|
|
// get first project id to be used for next container
|
|
//
|
|
//
|
|
- err = q.findNextProjectID(basePath)
|
|
|
|
|
|
+ err = q.findNextProjectID(basePath, baseProjectID)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- logrus.Debugf("NewControl(%s): nextProjectID = %d", basePath, q.nextProjectID)
|
|
|
|
|
|
+ logrus.Debugf("NewControl(%s): nextProjectID = %d", basePath, state.nextProjectID)
|
|
return &q, nil
|
|
return &q, nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -157,19 +190,24 @@ func (q *Control) SetQuota(targetPath string, quota Quota) error {
|
|
projectID, ok := q.quotas[targetPath]
|
|
projectID, ok := q.quotas[targetPath]
|
|
q.RUnlock()
|
|
q.RUnlock()
|
|
if !ok {
|
|
if !ok {
|
|
- q.Lock()
|
|
|
|
- projectID = q.nextProjectID
|
|
|
|
|
|
+ state := getPquotaState()
|
|
|
|
+ state.Lock()
|
|
|
|
+ projectID = state.nextProjectID
|
|
|
|
|
|
//
|
|
//
|
|
// assign project id to new container directory
|
|
// assign project id to new container directory
|
|
//
|
|
//
|
|
err := setProjectID(targetPath, projectID)
|
|
err := setProjectID(targetPath, projectID)
|
|
if err != nil {
|
|
if err != nil {
|
|
- q.Unlock()
|
|
|
|
|
|
+ state.Unlock()
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ state.nextProjectID++
|
|
|
|
+ state.Unlock()
|
|
|
|
+
|
|
|
|
+ q.Lock()
|
|
q.quotas[targetPath] = projectID
|
|
q.quotas[targetPath] = projectID
|
|
- q.nextProjectID++
|
|
|
|
q.Unlock()
|
|
q.Unlock()
|
|
}
|
|
}
|
|
|
|
|
|
@@ -279,9 +317,25 @@ func setProjectID(targetPath string, projectID uint32) error {
|
|
|
|
|
|
// findNextProjectID - find the next project id to be used for containers
|
|
// findNextProjectID - find the next project id to be used for containers
|
|
// by scanning driver home directory to find used project ids
|
|
// by scanning driver home directory to find used project ids
|
|
-func (q *Control) findNextProjectID(home string) error {
|
|
|
|
- q.Lock()
|
|
|
|
- defer q.Unlock()
|
|
|
|
|
|
+func (q *Control) findNextProjectID(home string, baseID uint32) error {
|
|
|
|
+ state := getPquotaState()
|
|
|
|
+ state.Lock()
|
|
|
|
+ defer state.Unlock()
|
|
|
|
+
|
|
|
|
+ checkProjID := func(path string) (uint32, error) {
|
|
|
|
+ projid, err := getProjectID(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return projid, err
|
|
|
|
+ }
|
|
|
|
+ if projid > 0 {
|
|
|
|
+ q.quotas[path] = projid
|
|
|
|
+ }
|
|
|
|
+ if state.nextProjectID <= projid {
|
|
|
|
+ state.nextProjectID = projid + 1
|
|
|
|
+ }
|
|
|
|
+ return projid, nil
|
|
|
|
+ }
|
|
|
|
+
|
|
files, err := ioutil.ReadDir(home)
|
|
files, err := ioutil.ReadDir(home)
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Errorf("read directory failed: %s", home)
|
|
return errors.Errorf("read directory failed: %s", home)
|
|
@@ -291,15 +345,26 @@ func (q *Control) findNextProjectID(home string) error {
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
path := filepath.Join(home, file.Name())
|
|
path := filepath.Join(home, file.Name())
|
|
- projid, err := getProjectID(path)
|
|
|
|
|
|
+ projid, err := checkProjID(path)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- if projid > 0 {
|
|
|
|
- q.quotas[path] = projid
|
|
|
|
|
|
+ if projid > 0 && projid != baseID {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ subfiles, err := ioutil.ReadDir(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return errors.Errorf("read directory failed: %s", path)
|
|
}
|
|
}
|
|
- if q.nextProjectID <= projid {
|
|
|
|
- q.nextProjectID = projid + 1
|
|
|
|
|
|
+ for _, subfile := range subfiles {
|
|
|
|
+ if !subfile.IsDir() {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ subpath := filepath.Join(path, subfile.Name())
|
|
|
|
+ _, err := checkProjID(subpath)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|