Skip to content

Commit bccc586

Browse files
authored
feat(throttling): added lib functions for setting io limits at pod cgroup (#3)
Signed-off-by: [email protected]
1 parent 6083ef6 commit bccc586

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.14
55
require (
66
github.com/container-storage-interface/spec v1.1.0
77
github.com/gogo/protobuf v1.3.0 // indirect
8+
github.com/google/uuid v1.0.0
89
github.com/googleapis/gnostic v0.3.1 // indirect
910
github.com/imdario/mergo v0.3.7 // indirect
1011
github.com/json-iterator/go v1.1.8 // indirect

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
120120
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
121121
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
122122
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
123+
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
123124
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
124125
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
125126
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=

pkg/common/helpers/helpers.go

+29
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ limitations under the License.
1717
package helpers
1818

1919
import (
20+
"os"
2021
"strings"
22+
23+
"github.com/google/uuid"
2124
)
2225

2326
// GetCaseInsensitiveMap coercs the map's keys to lower case, which only works
@@ -43,3 +46,29 @@ func GetInsensitiveParameter(dict *map[string]string, key string) string {
4346
insensitiveDict := GetCaseInsensitiveMap(dict)
4447
return insensitiveDict[strings.ToLower(key)]
4548
}
49+
50+
func exists(path string) (os.FileInfo, bool) {
51+
info, err := os.Stat(path)
52+
if os.IsNotExist(err) {
53+
return nil, false
54+
}
55+
return info, true
56+
}
57+
58+
// FileExists checks if a file exists and is not a directory
59+
func FileExists(filepath string) bool {
60+
info, present := exists(filepath)
61+
return present && info.Mode().IsRegular()
62+
}
63+
64+
// DirExists checks if a directory exists
65+
func DirExists(path string) bool {
66+
info, present := exists(path)
67+
return present && info.IsDir()
68+
}
69+
70+
// IsValidUUID validates whether a string is a valid UUID
71+
func IsValidUUID(u string) bool {
72+
_, err := uuid.Parse(u)
73+
return err == nil
74+
}

pkg/device/iolimit/models.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2020 The OpenEBS Authors
3+
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+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package iolimit
18+
19+
type Request struct {
20+
DeviceName string
21+
PodUid string
22+
ContainerRuntime string
23+
IOLimit *IOMax
24+
}
25+
26+
type ValidRequest struct {
27+
FilePath string
28+
DeviceNumber *DeviceNumber
29+
IOMax *IOMax
30+
}
31+
32+
type IOMax struct {
33+
Riops uint64
34+
Wiops uint64
35+
Rbps uint64
36+
Wbps uint64
37+
}
38+
39+
type DeviceNumber struct {
40+
Major uint64
41+
Minor uint64
42+
}

pkg/device/iolimit/utils.go

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
Copyright 2020 The OpenEBS Authors
3+
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+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package iolimit
18+
19+
import (
20+
"github.com/openebs/lib-csi/pkg/common/errors"
21+
"github.com/openebs/lib-csi/pkg/common/helpers"
22+
"io/ioutil"
23+
"strconv"
24+
"strings"
25+
"syscall"
26+
)
27+
28+
const (
29+
baseCgroupPath = "/sys/fs/cgroup"
30+
)
31+
32+
// SetIOLimits sets iops, bps limits for a pod with uid podUid for accessing a device named deviceName
33+
// provided that the underlying cgroup used for pod namespacing is cgroup2 (cgroup v2)
34+
func SetIOLimits(request *Request) error {
35+
if !helpers.DirExists(baseCgroupPath) {
36+
return errors.New(baseCgroupPath + " does not exist")
37+
}
38+
if err := checkCgroupV2(); err != nil {
39+
return err
40+
}
41+
validRequest, err := validate(request)
42+
if err != nil {
43+
return err
44+
}
45+
err = setIOLimits(validRequest)
46+
return err
47+
}
48+
49+
func validate(request *Request) (*ValidRequest, error) {
50+
if !helpers.IsValidUUID(request.PodUid) {
51+
return nil, errors.New("Expected PodUid in UUID format, Got " + request.PodUid)
52+
}
53+
podCGPath, err := getPodCGroupPath(request.PodUid, request.ContainerRuntime)
54+
if err != nil {
55+
return nil, err
56+
}
57+
ioMaxFile := podCGPath + "/io.max"
58+
if !helpers.FileExists(ioMaxFile) {
59+
return nil, errors.New("io.max file is not present in pod CGroup")
60+
}
61+
deviceNumber, err := getDeviceNumber(request.DeviceName)
62+
if err != nil {
63+
return nil, errors.New("Device Major:Minor numbers could not be obtained")
64+
}
65+
return &ValidRequest{
66+
FilePath: ioMaxFile,
67+
DeviceNumber: deviceNumber,
68+
IOMax: request.IOLimit,
69+
}, nil
70+
}
71+
72+
func getPodCGroupPath(podUid string, cruntime string) (string, error) {
73+
switch cruntime {
74+
case "containerd":
75+
path, err := getContainerdCGPath(podUid)
76+
if err != nil {
77+
return "", err
78+
}
79+
return path, nil
80+
default:
81+
return "", errors.New(cruntime + " runtime support is not present")
82+
}
83+
84+
}
85+
86+
func checkCgroupV2() error {
87+
if !helpers.FileExists(baseCgroupPath + "/cgroup.controllers") {
88+
return errors.New("CGroupV2 not enabled")
89+
}
90+
return nil
91+
}
92+
93+
func getContainerdPodCGSuffix(podUid string) string {
94+
return "pod" + strings.ReplaceAll(podUid, "-", "_")
95+
}
96+
97+
func getContainerdCGPath(podUid string) (string, error) {
98+
kubepodsCGPath := baseCgroupPath + "/kubepods.slice"
99+
podSuffix := getContainerdPodCGSuffix(podUid)
100+
podCGPath := kubepodsCGPath + "/kubepods-besteffort.slice/kubepods-besteffort-" + podSuffix + ".slice"
101+
if helpers.DirExists(podCGPath) {
102+
return podCGPath, nil
103+
}
104+
podCGPath = kubepodsCGPath + "/kubepods-burstable.slice/kubepods-burstable-" + podSuffix + ".slice"
105+
if helpers.DirExists(podCGPath) {
106+
return podCGPath, nil
107+
}
108+
return "", errors.New("CGroup Path not found for pod with Uid: " + podUid)
109+
}
110+
111+
func getDeviceNumber(deviceName string) (*DeviceNumber, error) {
112+
stat := syscall.Stat_t{}
113+
if err := syscall.Stat(deviceName, &stat); err != nil {
114+
return nil, err
115+
}
116+
return &DeviceNumber{
117+
Major: uint64(stat.Rdev/256),
118+
Minor: uint64(stat.Rdev%256),
119+
}, nil
120+
}
121+
122+
func getIOLimitsStr(deviceNumber *DeviceNumber, ioMax *IOMax) string {
123+
line := strconv.FormatUint(deviceNumber.Major, 10) + ":" + strconv.FormatUint(deviceNumber.Minor, 10)
124+
if ioMax.Riops != 0 {
125+
line += " riops=" + strconv.FormatUint(ioMax.Riops, 10)
126+
}
127+
if ioMax.Wiops != 0 {
128+
line += " wiops=" + strconv.FormatUint(ioMax.Wiops, 10)
129+
}
130+
if ioMax.Rbps != 0 {
131+
line += " rbps=" + strconv.FormatUint(ioMax.Rbps, 10)
132+
}
133+
if ioMax.Wbps != 0 {
134+
line += " wbps=" + strconv.FormatUint(ioMax.Wbps, 10)
135+
}
136+
return line
137+
}
138+
139+
func setIOLimits(request *ValidRequest) error {
140+
line := getIOLimitsStr(request.DeviceNumber, request.IOMax)
141+
err := ioutil.WriteFile(request.FilePath, []byte(line), 0700)
142+
return err
143+
}

0 commit comments

Comments
 (0)