github.com/intel/goresctrl@v0.5.0/pkg/blockio/blockio_test.go (about) 1 // Copyright 2019-2021 Intel Corporation. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package blockio 16 17 import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 "strings" 22 "testing" 23 24 "github.com/intel/goresctrl/pkg/cgroups" 25 "github.com/intel/goresctrl/pkg/testutils" 26 ) 27 28 var knownIOSchedulers map[string]bool = map[string]bool{ 29 "bfq": true, 30 "cfq": true, 31 "deadline": true, 32 "kyber": true, 33 "mq-deadline": true, 34 "none": true, 35 "noop": true, 36 } 37 38 // TestSetConfig: unit tests for SetConfigFromFile(), SetConfigFromData(), and SetConfig(). 39 func TestSetConfig(t *testing.T) { 40 initialConf := map[string]cgroups.BlockIOParameters{ 41 "classname": cgroups.BlockIOParameters{}, 42 } 43 emptyConf := map[string]cgroups.BlockIOParameters{} 44 goodConf := map[string]cgroups.BlockIOParameters{ 45 "goodclass": cgroups.NewBlockIOParameters(), 46 } 47 classBlockIO = copyConf(initialConf) 48 49 err := SetConfigFromFile("/blockio-test/non-existent-file", true) 50 testutils.VerifyError(t, err, 1, []string{"/blockio-test/non-existent-file", "failed to read"}) 51 testutils.VerifyDeepEqual(t, "effective configuration 1", initialConf, classBlockIO) 52 53 badConfFile := testutils.CreateTempFile(t, "bad config contents.\n") 54 emptyConfFile := testutils.CreateTempFile(t, "") 55 goodConfFile := testutils.CreateTempFile(t, "Classes:\n goodclass:\n") 56 defer os.Remove(badConfFile) 57 defer os.Remove(emptyConfFile) 58 defer os.Remove(goodConfFile) 59 60 for syntaxerror := 0; syntaxerror < 4; syntaxerror++ { 61 classBlockIO, err = copyConf(initialConf), nil 62 switch syntaxerror { 63 case 0: 64 err = SetConfigFromFile(badConfFile, false) 65 case 1: 66 err = SetConfigFromFile(badConfFile, true) 67 case 2: 68 err = SetConfigFromData([]byte("bad config."), false) 69 case 3: 70 err = SetConfigFromData([]byte("bad config."), true) 71 } 72 if syntaxerror < 2 { 73 testutils.VerifyError(t, err, 1, []string{badConfFile}) 74 } 75 testutils.VerifyError(t, err, 1, []string{"error unmarshaling"}) 76 testutils.VerifyDeepEqual(t, 77 fmt.Sprintf("syntax error configuration %d", syntaxerror), 78 initialConf, classBlockIO) 79 } 80 81 // Test valid ways to clear (reset) all classes 82 for clear := 0; clear < 8; clear++ { 83 classBlockIO, err = copyConf(initialConf), nil 84 switch clear { 85 case 0: 86 err = SetConfigFromFile(emptyConfFile, false) 87 case 1: 88 err = SetConfigFromFile(emptyConfFile, true) 89 case 2: 90 err = SetConfigFromData([]byte(""), false) 91 case 3: 92 err = SetConfigFromData([]byte(""), true) 93 case 4: 94 err = SetConfig(nil, false) 95 case 5: 96 err = SetConfig(nil, true) 97 case 6: 98 err = SetConfig(&Config{}, false) 99 case 7: 100 err = SetConfig(&Config{}, true) 101 } 102 testutils.VerifyNoError(t, err) 103 testutils.VerifyDeepEqual(t, 104 fmt.Sprintf("clear conf %d", clear), 105 emptyConf, classBlockIO) 106 } 107 108 err = SetConfigFromFile(goodConfFile, true) 109 testutils.VerifyNoError(t, err) 110 testutils.VerifyDeepEqual(t, "ok conf", goodConf, classBlockIO) 111 } 112 113 // copyConf returns a shallow copy of blockio class configuration. 114 func copyConf(orig map[string]cgroups.BlockIOParameters) map[string]cgroups.BlockIOParameters { 115 result := map[string]cgroups.BlockIOParameters{} 116 for key, value := range orig { 117 result[key] = value 118 } 119 return result 120 } 121 122 func TestClassNames(t *testing.T) { 123 classBlockIO = map[string]cgroups.BlockIOParameters{ 124 "a": cgroups.BlockIOParameters{}, 125 "z": cgroups.BlockIOParameters{}, 126 "b": cgroups.BlockIOParameters{}, 127 "x": cgroups.BlockIOParameters{}, 128 "c": cgroups.BlockIOParameters{}, 129 "d": cgroups.BlockIOParameters{}, 130 } 131 classes := GetClasses() 132 testutils.VerifyStringSlices(t, 133 []string{"a", "b", "c", "d", "x", "z"}, 134 classes) 135 classBlockIO = map[string]cgroups.BlockIOParameters{} 136 classes = GetClasses() 137 testutils.VerifyStringSlices(t, 138 []string{}, 139 classes) 140 } 141 142 // TestGetCurrentIOSchedulers: unit test for getCurrentIOSchedulers(). 143 func TestGetCurrentIOSchedulers(t *testing.T) { 144 currentIOSchedulers, err := getCurrentIOSchedulers() 145 testutils.VerifyError(t, err, 0, nil) 146 for blockDev, ioScheduler := range currentIOSchedulers { 147 s, ok := knownIOSchedulers[ioScheduler] 148 if !ok || !s { 149 t.Errorf("unknown io scheduler %#v on block device %#v", ioScheduler, blockDev) 150 } 151 } 152 } 153 154 // TestConfigurableBlockDevices: unit tests for configurableBlockDevices(). 155 func TestConfigurableBlockDevices(t *testing.T) { 156 sysfsBlockDevs, err := filepath.Glob("/sys/block/*") 157 if err != nil { 158 sysfsBlockDevs = []string{} 159 } 160 devBlockDevs := []string{} 161 for _, sysfsBlockDev := range sysfsBlockDevs { 162 if strings.HasPrefix(sysfsBlockDev, "/sys/block/sd") || strings.HasPrefix(sysfsBlockDev, "/sys/block/vd") { 163 devBlockDevs = append(devBlockDevs, strings.Replace(sysfsBlockDev, "/sys/block/", "/dev/", 1)) 164 } 165 } 166 t.Logf("test real block devices: %v", devBlockDevs) 167 tcases := []struct { 168 name string 169 devWildcards []string 170 expectedErrorCount int 171 expectedErrorSubstrings []string 172 expectedMatches int 173 disabled bool 174 disabledReason string 175 }{ 176 { 177 name: "no device wildcards", 178 devWildcards: nil, 179 expectedErrorCount: 0, 180 }, 181 { 182 name: "bad wildcard", 183 devWildcards: []string{"/[-/verybadwildcard]"}, 184 expectedErrorCount: 1, 185 expectedErrorSubstrings: []string{"verybadwildcard", "syntax error"}, 186 }, 187 { 188 name: "not matching wildcard", 189 devWildcards: []string{"/dev/path that should not exist/*"}, 190 expectedErrorCount: 1, 191 expectedErrorSubstrings: []string{"does not match any"}, 192 }, 193 { 194 name: "two wildcards: empty string and a character device", 195 devWildcards: []string{"/dev/null", ""}, 196 expectedErrorCount: 2, 197 expectedErrorSubstrings: []string{"\"/dev/null\" is a character device", "\"\" does not match any"}, 198 }, 199 { 200 name: "not a device or even a file", 201 devWildcards: []string{"/proc", "/proc/meminfo", "/proc/notexistingfile"}, 202 expectedErrorCount: 3, 203 expectedErrorSubstrings: []string{"\"/proc\" is not a device", "\"/proc/meminfo\" is not a device"}, 204 }, 205 { 206 name: "real block devices", 207 devWildcards: devBlockDevs, 208 expectedMatches: len(devBlockDevs), 209 }, 210 } 211 for _, tc := range tcases { 212 t.Run(tc.name, func(t *testing.T) { 213 if tc.disabled { 214 t.Skip(tc.disabledReason) 215 } 216 realPlatform := defaultPlatform{} 217 bdis, err := realPlatform.configurableBlockDevices(tc.devWildcards) 218 testutils.VerifyError(t, err, tc.expectedErrorCount, tc.expectedErrorSubstrings) 219 if len(bdis) != tc.expectedMatches { 220 t.Errorf("expected %d matching block devices, got %d", tc.expectedMatches, len(bdis)) 221 } 222 }) 223 } 224 } 225 226 // TestDevicesParametersToCgBlockIO: unit tests for devicesParametersToCgBlockIO(). 227 func TestDevicesParametersToCgBlockIO(t *testing.T) { 228 // switch real devicesParametersToCgBlockIO to call mockPlatform.configurableBlockDevices 229 currentPlatform = mockPlatform{} 230 tcases := []struct { 231 name string 232 dps []DevicesParameters 233 iosched map[string]string 234 expectedOci *cgroups.BlockIOParameters 235 expectedErrorCount int 236 expectedErrorSubstrings []string 237 }{ 238 { 239 name: "all OCI fields", 240 dps: []DevicesParameters{ 241 { 242 Weight: "144", 243 }, 244 { 245 Devices: []string{"/dev/sda"}, 246 ThrottleReadBps: "1G", 247 ThrottleWriteBps: "2M", 248 ThrottleReadIOPS: "3k", 249 ThrottleWriteIOPS: "4", 250 Weight: "50", 251 }, 252 }, 253 iosched: map[string]string{"/dev/sda": "bfq"}, 254 expectedOci: &cgroups.BlockIOParameters{ 255 Weight: 144, 256 WeightDevice: cgroups.DeviceWeights{ 257 {Major: 11, Minor: 12, Weight: 50}, 258 }, 259 ThrottleReadBpsDevice: cgroups.DeviceRates{ 260 {Major: 11, Minor: 12, Rate: 1000000000}, 261 }, 262 ThrottleWriteBpsDevice: cgroups.DeviceRates{ 263 {Major: 11, Minor: 12, Rate: 2000000}, 264 }, 265 ThrottleReadIOPSDevice: cgroups.DeviceRates{ 266 {Major: 11, Minor: 12, Rate: 3000}, 267 }, 268 ThrottleWriteIOPSDevice: cgroups.DeviceRates{ 269 {Major: 11, Minor: 12, Rate: 4}, 270 }, 271 }, 272 }, 273 { 274 name: "later match overrides value", 275 dps: []DevicesParameters{ 276 { 277 Devices: []string{"/dev/sda", "/dev/sdb", "/dev/sdc"}, 278 ThrottleReadBps: "100", 279 Weight: "110", 280 }, 281 { 282 Devices: []string{"/dev/sdb", "/dev/sdc"}, 283 ThrottleReadBps: "300", 284 Weight: "330", 285 }, 286 { 287 Devices: []string{"/dev/sdb"}, 288 ThrottleReadBps: "200", 289 Weight: "220", 290 }, 291 }, 292 iosched: map[string]string{"/dev/sda": "bfq", "/dev/sdb": "bfq", "/dev/sdc": "cfq"}, 293 expectedOci: &cgroups.BlockIOParameters{ 294 Weight: -1, 295 WeightDevice: cgroups.DeviceWeights{ 296 {Major: 11, Minor: 12, Weight: 110}, 297 {Major: 21, Minor: 22, Weight: 220}, 298 {Major: 31, Minor: 32, Weight: 330}, 299 }, 300 ThrottleReadBpsDevice: cgroups.DeviceRates{ 301 {Major: 11, Minor: 12, Rate: 100}, 302 {Major: 21, Minor: 22, Rate: 200}, 303 {Major: 31, Minor: 32, Rate: 300}, 304 }, 305 }, 306 }, 307 { 308 name: "invalid weights, many errors in different parameter sets", 309 dps: []DevicesParameters{ 310 { 311 Weight: "99999", 312 }, 313 { 314 Devices: []string{"/dev/sda"}, 315 Weight: "1", 316 }, 317 { 318 Devices: []string{"/dev/sdb"}, 319 Weight: "-2", 320 }, 321 }, 322 expectedErrorCount: 3, 323 expectedErrorSubstrings: []string{ 324 "(99999) bigger than maximum", 325 "(1) smaller than minimum", 326 "(-2) smaller than minimum", 327 }, 328 }, 329 { 330 name: "throttling without listing Devices", 331 dps: []DevicesParameters{ 332 { 333 ThrottleReadBps: "100M", 334 ThrottleWriteIOPS: "20k", 335 }, 336 }, 337 expectedErrorCount: 1, 338 expectedErrorSubstrings: []string{ 339 "Devices not listed", 340 "\"100M\"", 341 "\"20k\"", 342 }, 343 }, 344 } 345 for _, tc := range tcases { 346 t.Run(tc.name, func(t *testing.T) { 347 oci, err := devicesParametersToCgBlockIO(tc.dps, tc.iosched) 348 testutils.VerifyError(t, err, tc.expectedErrorCount, tc.expectedErrorSubstrings) 349 if tc.expectedOci != nil { 350 testutils.VerifyDeepEqual(t, "OCI parameters", *tc.expectedOci, oci) 351 } 352 }) 353 } 354 } 355 356 // mockPlatform implements mock versions of platformInterface functions. 357 type mockPlatform struct{} 358 359 // configurableBlockDevices mock always returns a set of block devices. 360 func (mpf mockPlatform) configurableBlockDevices(devWildcards []string) ([]tBlockDeviceInfo, error) { 361 blockDevices := []tBlockDeviceInfo{} 362 for _, devWildcard := range devWildcards { 363 if devWildcard == "/dev/sda" { 364 blockDevices = append(blockDevices, tBlockDeviceInfo{ 365 Major: 11, 366 Minor: 12, 367 DevNode: devWildcard, 368 Origin: fmt.Sprintf("from wildcards %v", devWildcard), 369 }) 370 } else if devWildcard == "/dev/sdb" { 371 blockDevices = append(blockDevices, tBlockDeviceInfo{ 372 Major: 21, 373 Minor: 22, 374 DevNode: devWildcard, 375 Origin: fmt.Sprintf("from wildcards %v", devWildcard), 376 }) 377 } else if devWildcard == "/dev/sdc" { 378 blockDevices = append(blockDevices, tBlockDeviceInfo{ 379 Major: 31, 380 Minor: 32, 381 DevNode: devWildcard, 382 Origin: fmt.Sprintf("from wildcards %v", devWildcard), 383 }) 384 } 385 } 386 return blockDevices, nil 387 }