
This patch adds support for a container annotation and two separate
pod annotations for controlling the blockio class of containers.
The container annotation can be used by a CRI client:
"io.kubernetes.cri.blockio-class"
Pod annotations specify the blockio class in the K8s pod spec level:
"blockio.resources.beta.kubernetes.io/pod"
(pod-wide default for all containers within)
"blockio.resources.beta.kubernetes.io/container.<container_name>"
(container-specific overrides)
Correspondingly, this patch adds support for --blockio-class and
--blockio-config-file to ctr, too.
This implementation follows the resource class annotation pattern
introduced in RDT and merged in commit 893701220
.
Signed-off-by: Antti Kervinen <antti.kervinen@intel.com>
231 lines
5.3 KiB
Go
231 lines
5.3 KiB
Go
// Copyright 2021 Intel Corporation. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// This module implements a mock filesystem that can be used as a
|
|
// replacement for the native filesystem interface (fsi).
|
|
|
|
package cgroups
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type fsMock struct {
|
|
files map[string]*mockFile // filesystem contents
|
|
}
|
|
|
|
type mockFile struct {
|
|
// User-defined file properties
|
|
data []byte // contents of the file
|
|
|
|
// User/fsimock-defined properties
|
|
info *mockFileInfo
|
|
|
|
// File-specific user-overrides for the default file behavior
|
|
open func(string) (fileIface, error)
|
|
read func([]byte) (int, error)
|
|
write func([]byte) (int, error)
|
|
|
|
// fsimock-defined properties
|
|
fs *fsMock
|
|
filename string
|
|
handle *mockFileHandle
|
|
writeHistory [][]byte
|
|
}
|
|
|
|
type mockFileHandle struct {
|
|
pos int
|
|
}
|
|
|
|
type mockFileInfo struct {
|
|
mode os.FileMode
|
|
name string
|
|
mf *mockFile
|
|
}
|
|
|
|
func NewFsiMock(files map[string]mockFile) fsiIface {
|
|
mfs := fsMock{}
|
|
mfs.files = map[string]*mockFile{}
|
|
for filename, usermf := range files {
|
|
mf := usermf
|
|
if mf.info == nil {
|
|
mf.info = &mockFileInfo{}
|
|
}
|
|
if mf.info.name == "" {
|
|
mf.info.name = filepath.Base(filename)
|
|
}
|
|
mf.filename = filename
|
|
mf.info.mf = &mf
|
|
mf.fs = &mfs
|
|
mfs.files[filename] = &mf
|
|
}
|
|
return &mfs
|
|
}
|
|
|
|
func (mfs fsMock) OpenFile(name string, flag int, perm os.FileMode) (fileIface, error) {
|
|
fsmockLog("OpenFile(%q, %d, %d)", name, flag, perm)
|
|
if mf, ok := mfs.files[name]; ok {
|
|
mf.handle = &mockFileHandle{}
|
|
if mf.open != nil {
|
|
return mf.open(name)
|
|
}
|
|
return *mf, nil
|
|
}
|
|
return nil, fsmockErrorf("%q: file not found", name)
|
|
}
|
|
|
|
func (mfs fsMock) Open(name string) (fileIface, error) {
|
|
return mfs.OpenFile(name, 0, 0)
|
|
}
|
|
|
|
func (mfs fsMock) Walk(path string, walkFn filepath.WalkFunc) error {
|
|
dirPath := strings.TrimSuffix(path, "/")
|
|
info, err := mfs.Lstat(dirPath)
|
|
if err != nil {
|
|
err = walkFn(path, nil, err)
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
return walkFn(path, info, nil)
|
|
}
|
|
err = walkFn(path, info, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, name := range mfs.dirContents(dirPath) {
|
|
if err = mfs.Walk(dirPath+"/"+name, walkFn); err != nil && err != filepath.SkipDir {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mfs fsMock) dirContents(path string) []string {
|
|
dirPathS := strings.TrimSuffix(path, "/") + "/"
|
|
contentSet := map[string]struct{}{}
|
|
for filename := range mfs.files {
|
|
if !strings.HasPrefix(filename, dirPathS) {
|
|
continue
|
|
}
|
|
relToDirPath := strings.TrimPrefix(filename, dirPathS)
|
|
names := strings.SplitN(relToDirPath, "/", 2)
|
|
contentSet[names[0]] = struct{}{}
|
|
}
|
|
contents := make([]string, 0, len(contentSet))
|
|
for name := range contentSet {
|
|
contents = append(contents, name)
|
|
}
|
|
return contents
|
|
}
|
|
|
|
func (mfs fsMock) Lstat(path string) (os.FileInfo, error) {
|
|
if mf, ok := mfs.files[path]; ok {
|
|
return *mf.info, nil
|
|
}
|
|
if len(mfs.dirContents(path)) > 0 {
|
|
return mockFileInfo{
|
|
name: filepath.Base(path),
|
|
mode: os.ModeDir,
|
|
}, nil
|
|
}
|
|
return mockFileInfo{}, fsmockErrorf("%q: file not found", path)
|
|
}
|
|
|
|
func (mfi mockFileInfo) Name() string {
|
|
return mfi.name
|
|
}
|
|
func (mfi mockFileInfo) Size() int64 {
|
|
if mfi.mf != nil {
|
|
return int64(len(mfi.mf.data))
|
|
}
|
|
return 0
|
|
}
|
|
func (mfi mockFileInfo) Mode() os.FileMode {
|
|
return mfi.mode
|
|
}
|
|
|
|
func (mfi mockFileInfo) ModTime() time.Time {
|
|
return time.Time{}
|
|
}
|
|
|
|
func (mfi mockFileInfo) IsDir() bool {
|
|
return mfi.mode&os.ModeDir != 0
|
|
}
|
|
|
|
func (mfi mockFileInfo) Sys() interface{} {
|
|
return nil
|
|
}
|
|
|
|
func (mf mockFile) Write(b []byte) (n int, err error) {
|
|
pos := mf.handle.pos
|
|
if mf.write != nil {
|
|
n, err = mf.write(b)
|
|
if err == nil {
|
|
mf.fs.files[mf.filename].writeHistory = append(mf.fs.files[mf.filename].writeHistory, b)
|
|
}
|
|
} else {
|
|
newpos := pos + len(b)
|
|
if newpos > cap(mf.data) {
|
|
newdata := make([]byte, newpos)
|
|
copy(newdata, mf.data)
|
|
mf.data = newdata
|
|
}
|
|
copy(mf.data[pos:newpos], b)
|
|
mf.handle.pos = newpos
|
|
if f, ok := mf.fs.files[mf.filename]; ok {
|
|
f.data = mf.data
|
|
}
|
|
mf.fs.files[mf.filename].writeHistory = append(mf.fs.files[mf.filename].writeHistory, b)
|
|
}
|
|
fsmockLog("{%q, pos=%d}.Write([%d]byte(%q)) = (%d, %v) %q", mf.filename, pos, len(b), string(b), n, err, mf.fs.files[mf.filename].data)
|
|
return n, err
|
|
}
|
|
|
|
func (mf mockFile) Read(b []byte) (n int, err error) {
|
|
pos := mf.handle.pos
|
|
if mf.read != nil {
|
|
n, err = mf.read(b)
|
|
} else {
|
|
n = len(mf.data) - pos
|
|
err = nil
|
|
if n <= 0 {
|
|
err = io.EOF
|
|
}
|
|
if n > cap(b) {
|
|
n = cap(b)
|
|
}
|
|
copy(b, mf.data[pos:pos+n])
|
|
mf.handle.pos += n
|
|
}
|
|
fsmockLog("{%q, pos=%d}.Read([%d]byte) = (%d, %v)\n", mf.filename, pos, len(b), n, err)
|
|
return
|
|
}
|
|
|
|
func (mf mockFile) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func fsmockLog(format string, args ...interface{}) {
|
|
fmt.Printf("fsmock: "+format+"\n", args...)
|
|
}
|
|
|
|
func fsmockErrorf(format string, args ...interface{}) error {
|
|
return fmt.Errorf("fsmock: "+format, args...)
|
|
}
|