github.com/intel/goresctrl@v0.5.0/pkg/cgroups/cgroupblkio_test.go (about)

     1  // Copyright 2020-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 cgroups
    16  
    17  import (
    18  	"bytes"
    19  	"io"
    20  	"syscall"
    21  	"testing"
    22  
    23  	"github.com/intel/goresctrl/pkg/testutils"
    24  )
    25  
    26  var mountDir = "/sys/fs/cgroup"
    27  
    28  func TestUpdateAppend(t *testing.T) {
    29  	tcases := []struct {
    30  		name                    string
    31  		inputMajMinVals         [][]int64
    32  		inputItem               []int64
    33  		expectedMajMinVal       [][]int64
    34  		expectedErrorCount      int
    35  		expectedErrorSubstrings []string
    36  	}{
    37  		{
    38  			name:              "update empty list",
    39  			inputItem:         []int64{1, 2, 3},
    40  			expectedMajMinVal: [][]int64{{1, 2, 3}},
    41  		},
    42  		{
    43  			name:              "update appends non-existing element",
    44  			inputMajMinVals:   [][]int64{{10, 20, 30}, {40, 50, 60}},
    45  			inputItem:         []int64{1, 2, 3},
    46  			expectedMajMinVal: [][]int64{{10, 20, 30}, {40, 50, 60}, {1, 2, 3}},
    47  		},
    48  		{
    49  			name:              "update the first existing element",
    50  			inputMajMinVals:   [][]int64{{10, 20, 30}, {40, 50, 60}, {40, 50, 60}},
    51  			inputItem:         []int64{40, 50, 66},
    52  			expectedMajMinVal: [][]int64{{10, 20, 30}, {40, 50, 66}, {40, 50, 60}},
    53  		},
    54  	}
    55  	for _, tc := range tcases {
    56  		t.Run(tc.name, func(t *testing.T) {
    57  			devWeights := DeviceWeights{}
    58  			devRates := DeviceRates{}
    59  			expDevWeights := DeviceWeights{}
    60  			expDevRates := DeviceRates{}
    61  			for _, item := range tc.inputMajMinVals {
    62  				devWeights.Append(item[0], item[1], item[2])
    63  				devRates.Append(item[0], item[1], item[2])
    64  			}
    65  			devWeights.Update(tc.inputItem[0], tc.inputItem[1], tc.inputItem[2])
    66  			devRates.Update(tc.inputItem[0], tc.inputItem[1], tc.inputItem[2])
    67  			for _, item := range tc.expectedMajMinVal {
    68  				expDevWeights = append(expDevWeights, DeviceWeight{item[0], item[1], item[2]})
    69  				expDevRates = append(expDevRates, DeviceRate{item[0], item[1], item[2]})
    70  			}
    71  			testutils.VerifyDeepEqual(t, "device weights", expDevWeights, devWeights)
    72  			testutils.VerifyDeepEqual(t, "device rates", expDevRates, devRates)
    73  		})
    74  	}
    75  }
    76  
    77  var fsBlkioUtFiles map[string]mockFile = map[string]mockFile{
    78  	mountDir + "/blkio/mockpods/clean/blkio.bfq.weight":                 {data: []byte("100\n")},
    79  	mountDir + "/blkio/mockpods/clean/blkio.bfq.weight_device":          {},
    80  	mountDir + "/blkio/mockpods/clean/blkio.throttle.read_bps_device":   {},
    81  	mountDir + "/blkio/mockpods/clean/blkio.throttle.write_bps_device":  {},
    82  	mountDir + "/blkio/mockpods/clean/blkio.throttle.read_iops_device":  {},
    83  	mountDir + "/blkio/mockpods/clean/blkio.throttle.write_iops_device": {},
    84  
    85  	mountDir + "/blkio/mockpods/no-blkio-bfq-weight/blkio.weight": {data: []byte("100\n")},
    86  
    87  	mountDir + "/blkio/mockpods/reset/blkio.bfq.weight":                 {data: []byte("200\n")},
    88  	mountDir + "/blkio/mockpods/reset/blkio.bfq.weight_device":          {data: []byte("default 200\n1:2 3\n4:5 6\n")},
    89  	mountDir + "/blkio/mockpods/reset/blkio.throttle.read_bps_device":   {data: []byte("11:12 13\n14:15 16\n")},
    90  	mountDir + "/blkio/mockpods/reset/blkio.throttle.write_bps_device":  {data: []byte("21:22 23\n")},
    91  	mountDir + "/blkio/mockpods/reset/blkio.throttle.read_iops_device":  {data: []byte("31:32 33\n")},
    92  	mountDir + "/blkio/mockpods/reset/blkio.throttle.write_iops_device": {data: []byte("41:42 43\n")},
    93  
    94  	mountDir + "/blkio/mockpods/merge/blkio.bfq.weight":                 {data: []byte("200\n")},
    95  	mountDir + "/blkio/mockpods/merge/blkio.bfq.weight_device":          {data: []byte("default 200\n1:2 3\n4:5 6\n7:8 9")},
    96  	mountDir + "/blkio/mockpods/merge/blkio.throttle.read_bps_device":   {data: []byte("11:12 13\n14:15 16\n")},
    97  	mountDir + "/blkio/mockpods/merge/blkio.throttle.write_bps_device":  {data: []byte("21:22 23\n24:25 26\n")},
    98  	mountDir + "/blkio/mockpods/merge/blkio.throttle.read_iops_device":  {data: []byte("31:32 33\n331:332 333\n")},
    99  	mountDir + "/blkio/mockpods/merge/blkio.throttle.write_iops_device": {data: []byte("41:42 43\n441:442 443\n")},
   100  
   101  	// parseok:
   102  	// test weight without linefeed
   103  	// test weight_device file with real "default" line
   104  	// test parsing two lines and skipping empty lines
   105  	// test single line file
   106  	// test single line, missing LF at the end
   107  	// test small and large values
   108  	mountDir + "/blkio/parseok/blkio.bfq.weight":                 {data: []byte("1")},
   109  	mountDir + "/blkio/parseok/blkio.bfq.weight_device":          {data: []byte("default 10\n1:2 3\n")},
   110  	mountDir + "/blkio/parseok/blkio.throttle.read_bps_device":   {data: []byte("\n11:22 33\n\n111:222 333\n")},
   111  	mountDir + "/blkio/parseok/blkio.throttle.write_bps_device":  {data: []byte("1111:2222 3333\n")},
   112  	mountDir + "/blkio/parseok/blkio.throttle.read_iops_device":  {data: []byte("11111:22222 33333")},
   113  	mountDir + "/blkio/parseok/blkio.throttle.write_iops_device": {data: []byte("0:0 0\n4294967296:4294967297 9223372036854775807\n")},
   114  
   115  	// parse-err:
   116  	// weight: not a number
   117  	// weight_device: test bad line in the middle
   118  	// read_bps_device: test no spaces
   119  	// write_bps_device: test too many spaces
   120  	// read_iobps_device: test no colons
   121  	// write_iobps_device: test missing number
   122  	mountDir + "/blkio/parse-err/blkio.bfq.weight":                 {data: []byte("xyz")},
   123  	mountDir + "/blkio/parse-err/blkio.bfq.weight_device":          {data: []byte("default 10\n1:2 3\nbad\n4:5 6\n")},
   124  	mountDir + "/blkio/parse-err/blkio.throttle.read_bps_device":   {data: []byte("11:22:33")},
   125  	mountDir + "/blkio/parse-err/blkio.throttle.write_bps_device":  {data: []byte("1111 2222 3333 \n")},
   126  	mountDir + "/blkio/parse-err/blkio.throttle.read_iops_device":  {data: []byte("1111122222 33333")},
   127  	mountDir + "/blkio/parse-err/blkio.throttle.write_iops_device": {data: []byte("0: 0\n")},
   128  
   129  	mountDir + "/blkio/write-enodev/blkio.bfq.weight":                 {write: func([]byte) (int, error) { return 0, syscall.ENODEV }},
   130  	mountDir + "/blkio/write-enodev/blkio.bfq.weight_device":          {write: func([]byte) (int, error) { return 0, syscall.ENODEV }},
   131  	mountDir + "/blkio/write-enodev/blkio.throttle.read_bps_device":   {write: func([]byte) (int, error) { return 0, syscall.ENODEV }},
   132  	mountDir + "/blkio/write-enodev/blkio.throttle.write_bps_device":  {write: func([]byte) (int, error) { return 0, syscall.ENODEV }},
   133  	mountDir + "/blkio/write-enodev/blkio.throttle.read_iops_device":  {write: func([]byte) (int, error) { return 0, syscall.ENODEV }},
   134  	mountDir + "/blkio/write-enodev/blkio.throttle.write_iops_device": {write: func([]byte) (int, error) { return 0, syscall.ENODEV }},
   135  }
   136  
   137  // TestResetBlkioParameters: unit test for ResetBlkioParameters()
   138  func TestResetBlkioParameters(t *testing.T) {
   139  	tcases := []struct {
   140  		name                    string
   141  		fsi                     fsiIface
   142  		cntnrDir                string
   143  		blockIO                 BlockIOParameters
   144  		expectedFsWrites        map[string][][]byte
   145  		expectedBlockIO         *BlockIOParameters
   146  		expectedErrorCount      int
   147  		expectedErrorSubstrings []string
   148  	}{
   149  		{
   150  			name:     "write to clean cgroups",
   151  			fsi:      NewFsiMock(fsBlkioUtFiles),
   152  			cntnrDir: "mockpods/clean",
   153  			blockIO: BlockIOParameters{
   154  				Weight:                  222,
   155  				WeightDevice:            DeviceWeights{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
   156  				ThrottleReadBpsDevice:   DeviceRates{{11, 12, 13}, {111, 112, 113}},
   157  				ThrottleWriteBpsDevice:  DeviceRates{{21, 22, 23}, {221, 222, 223}},
   158  				ThrottleReadIOPSDevice:  DeviceRates{{31, 32, 33}, {331, 332, 333}},
   159  				ThrottleWriteIOPSDevice: DeviceRates{{41, 42, 43}, {441, 442, 443}},
   160  			},
   161  			expectedFsWrites: map[string][][]byte{
   162  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight":                 {[]byte("222")},
   163  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight_device":          {[]byte("1:2 3"), []byte("4:5 6"), []byte("7:8 9")},
   164  				mountDir + "/blkio/mockpods/clean/blkio.throttle.read_bps_device":   {[]byte("11:12 13"), []byte("111:112 113")},
   165  				mountDir + "/blkio/mockpods/clean/blkio.throttle.write_bps_device":  {[]byte("21:22 23"), []byte("221:222 223")},
   166  				mountDir + "/blkio/mockpods/clean/blkio.throttle.read_iops_device":  {[]byte("31:32 33"), []byte("331:332 333")},
   167  				mountDir + "/blkio/mockpods/clean/blkio.throttle.write_iops_device": {[]byte("41:42 43"), []byte("441:442 443")},
   168  			},
   169  		},
   170  		{
   171  			name:     "reset all existing",
   172  			fsi:      NewFsiMock(fsBlkioUtFiles),
   173  			cntnrDir: "mockpods/reset",
   174  			blockIO:  NewBlockIOParameters(),
   175  			expectedFsWrites: map[string][][]byte{
   176  				mountDir + "/blkio/mockpods/reset/blkio.bfq.weight_device":          {[]byte("1:2 0"), []byte("4:5 0")},
   177  				mountDir + "/blkio/mockpods/reset/blkio.throttle.read_bps_device":   {[]byte("11:12 0"), []byte("14:15 0")},
   178  				mountDir + "/blkio/mockpods/reset/blkio.throttle.write_bps_device":  {[]byte("21:22 0")},
   179  				mountDir + "/blkio/mockpods/reset/blkio.throttle.read_iops_device":  {[]byte("31:32 0")},
   180  				mountDir + "/blkio/mockpods/reset/blkio.throttle.write_iops_device": {[]byte("41:42 0")},
   181  			},
   182  		},
   183  		{
   184  			name:     "merge",
   185  			fsi:      NewFsiMock(fsBlkioUtFiles),
   186  			cntnrDir: "mockpods/merge",
   187  			blockIO: BlockIOParameters{
   188  				Weight:                  80,
   189  				WeightDevice:            DeviceWeights{{1, 2, 1113}, {7, 8, 9}},       // drop middle, update first, keep last
   190  				ThrottleReadBpsDevice:   DeviceRates{{11, 12, 13}},                    // keep the first entry
   191  				ThrottleWriteBpsDevice:  DeviceRates{{24, 25, 26}},                    // keep the last entry
   192  				ThrottleReadIOPSDevice:  DeviceRates{{31, 32, 33}, {331, 332, 333}},   // keep all
   193  				ThrottleWriteIOPSDevice: DeviceRates{{41, 42, 430}, {441, 442, 4430}}, // change all
   194  			},
   195  			expectedFsWrites: map[string][][]byte{
   196  				mountDir + "/blkio/mockpods/merge/blkio.bfq.weight":                 {[]byte("80")},
   197  				mountDir + "/blkio/mockpods/merge/blkio.bfq.weight_device":          {[]byte("1:2 1113"), []byte("7:8 9"), []byte("4:5 0")},
   198  				mountDir + "/blkio/mockpods/merge/blkio.throttle.read_bps_device":   {[]byte("11:12 13"), []byte("14:15 0")},
   199  				mountDir + "/blkio/mockpods/merge/blkio.throttle.write_bps_device":  {[]byte("24:25 26"), []byte("21:22 0")},
   200  				mountDir + "/blkio/mockpods/merge/blkio.throttle.read_iops_device":  {[]byte("31:32 33"), []byte("331:332 333")},
   201  				mountDir + "/blkio/mockpods/merge/blkio.throttle.write_iops_device": {[]byte("41:42 430"), []byte("441:442 4430")},
   202  			},
   203  		},
   204  	}
   205  	for _, tc := range tcases {
   206  		t.Run(tc.name, func(t *testing.T) {
   207  			fsi = tc.fsi
   208  			err := ResetBlkioParameters(tc.cntnrDir, tc.blockIO)
   209  			testutils.VerifyError(t, err, tc.expectedErrorCount, tc.expectedErrorSubstrings)
   210  			validateWriteHistory(t, tc.expectedFsWrites, fsi.(*fsMock).files)
   211  		})
   212  	}
   213  }
   214  
   215  // validateWriteHistory compares expected writes to filesystem to all
   216  // observed writes.
   217  func validateWriteHistory(t *testing.T, expected map[string][][]byte, filesystem map[string]*mockFile) {
   218  	for expFilename, expWrites := range expected {
   219  		mf, ok := filesystem[expFilename]
   220  		if !ok {
   221  			t.Errorf("expected writes to %q, but file is missing", expFilename)
   222  			return
   223  		}
   224  		obsWrites := mf.writeHistory
   225  		if len(expWrites) != len(obsWrites) {
   226  			t.Errorf("unexpected number of writes to %q: expected %v, observed %v", expFilename, expWrites, obsWrites)
   227  			return
   228  		}
   229  		for i, expWrite := range expWrites {
   230  			if !bytes.Equal(expWrite, obsWrites[i]) {
   231  				t.Errorf("write at index %d differs: expected %v, observed %v", i, expWrites, obsWrites)
   232  			}
   233  		}
   234  	}
   235  	for obsFilename, mf := range filesystem {
   236  		if mf.writeHistory != nil {
   237  			if _, ok := expected[obsFilename]; !ok {
   238  				t.Errorf("writes to unexpected file %q, observed: %v", obsFilename, mf.writeHistory)
   239  			}
   240  		}
   241  	}
   242  }
   243  
   244  // TestGetBlkioParameters: unit test for GetBlkioParameters()
   245  func TestGetBlkioParameters(t *testing.T) {
   246  	tcases := []struct {
   247  		name                    string
   248  		fsi                     fsiIface
   249  		fsFuncs                 map[string]mockFile
   250  		cntnrDir                string
   251  		readsFail               int
   252  		fsContent               map[string]string
   253  		expectedBlockIO         *BlockIOParameters
   254  		expectedErrorCount      int
   255  		expectedErrorSubstrings []string
   256  	}{
   257  		{
   258  			name: "all clean and empty",
   259  			fsi:  NewFsiMock(fsBlkioUtFiles),
   260  			fsFuncs: map[string]mockFile{
   261  				// reuse clean directory, but force weight file empty
   262  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight": {
   263  					read: func([]byte) (int, error) { return 0, io.EOF },
   264  				},
   265  			},
   266  			cntnrDir:                "mockpods/clean",
   267  			expectedBlockIO:         &BlockIOParameters{Weight: -1},
   268  			expectedErrorCount:      1, // weight is not expected to be empty
   269  			expectedErrorSubstrings: []string{"parsing weight"},
   270  		},
   271  		{
   272  			name:     "everything defined",
   273  			fsi:      NewFsiMock(fsBlkioUtFiles),
   274  			cntnrDir: "/parseok",
   275  			expectedBlockIO: &BlockIOParameters{
   276  				Weight:                  1,
   277  				WeightDevice:            DeviceWeights{{1, 2, 3}},
   278  				ThrottleReadBpsDevice:   DeviceRates{{11, 22, 33}, {111, 222, 333}},
   279  				ThrottleWriteBpsDevice:  DeviceRates{{1111, 2222, 3333}},
   280  				ThrottleReadIOPSDevice:  DeviceRates{{11111, 22222, 33333}},
   281  				ThrottleWriteIOPSDevice: DeviceRates{{0, 0, 0}, {4294967296, 4294967297, 9223372036854775807}},
   282  			},
   283  		},
   284  		{
   285  			name:                    "test bad files",
   286  			fsi:                     NewFsiMock(fsBlkioUtFiles),
   287  			cntnrDir:                "/parse-err",
   288  			expectedErrorCount:      6,
   289  			expectedErrorSubstrings: []string{"bad", "xyz", "11:22:33", "1111 2222 3333 ", "1111122222 33333", "0: 0"},
   290  			expectedBlockIO: &BlockIOParameters{
   291  				Weight:       -1,
   292  				WeightDevice: DeviceWeights{{1, 2, 3}, {4, 5, 6}},
   293  			},
   294  		},
   295  		{
   296  			name:               "all files missing",
   297  			fsi:                NewFsiMock(fsBlkioUtFiles),
   298  			cntnrDir:           "/this/container/does/not/exist",
   299  			expectedBlockIO:    &BlockIOParameters{Weight: -1},
   300  			expectedErrorCount: 6,
   301  			expectedErrorSubstrings: []string{
   302  				"file not found",
   303  				"blkio.bfq.weight",
   304  				"blkio.bfq.weight_device",
   305  				"blkio.throttle.read_bps_device",
   306  				"blkio.throttle.write_bps_device",
   307  				"blkio.throttle.read_iops_device",
   308  				"blkio.throttle.write_iops_device",
   309  			},
   310  		},
   311  	}
   312  
   313  	for _, tc := range tcases {
   314  		t.Run(tc.name, func(t *testing.T) {
   315  			fsi = tc.fsi
   316  			overrideFsFuncs(fsi.(*fsMock), tc.fsFuncs)
   317  			blockIO, err := GetBlkioParameters(tc.cntnrDir)
   318  			testutils.VerifyError(t, err, tc.expectedErrorCount, tc.expectedErrorSubstrings)
   319  			if tc.expectedBlockIO != nil {
   320  				testutils.VerifyDeepEqual(t, "blockio parameters", *tc.expectedBlockIO, blockIO)
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  // overrideFsFuncs (re)sets user overrides of file-specific functions
   327  // in the mock filesystem.
   328  func overrideFsFuncs(fsm *fsMock, fsFuncs map[string]mockFile) {
   329  	for filename, mf := range fsFuncs {
   330  		if mf.open != nil {
   331  			fsm.files[filename].open = mf.open
   332  		}
   333  		if mf.read != nil {
   334  			fsm.files[filename].read = mf.read
   335  		}
   336  		if mf.write != nil {
   337  			fsm.files[filename].write = mf.write
   338  		}
   339  	}
   340  }
   341  
   342  // TestSetBlkioParameters: unit test for SetBlkioParameters()
   343  func TestSetBlkioParameters(t *testing.T) {
   344  	tcases := []struct {
   345  		name                    string
   346  		fsi                     fsiIface
   347  		fsFuncs                 map[string]mockFile
   348  		cntnrDir                string
   349  		blockIO                 BlockIOParameters
   350  		writesFail              int
   351  		expectedFsWrites        map[string][][]byte
   352  		expectedErrorCount      int
   353  		expectedErrorSubstrings []string
   354  	}{
   355  		{
   356  			name:     "write full OCI struct",
   357  			fsi:      NewFsiMock(fsBlkioUtFiles),
   358  			cntnrDir: "/mockpods/clean",
   359  			blockIO: BlockIOParameters{
   360  				Weight:                  10,
   361  				WeightDevice:            DeviceWeights{{Major: 1, Minor: 2, Weight: 3}},
   362  				ThrottleReadBpsDevice:   DeviceRates{{Major: 11, Minor: 12, Rate: 13}},
   363  				ThrottleWriteBpsDevice:  DeviceRates{{Major: 21, Minor: 22, Rate: 23}},
   364  				ThrottleReadIOPSDevice:  DeviceRates{{Major: 31, Minor: 32, Rate: 33}},
   365  				ThrottleWriteIOPSDevice: DeviceRates{{Major: 41, Minor: 42, Rate: 43}},
   366  			},
   367  			expectedFsWrites: map[string][][]byte{
   368  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight":                 {[]byte("10")},
   369  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight_device":          {[]byte("1:2 3")},
   370  				mountDir + "/blkio/mockpods/clean/blkio.throttle.read_bps_device":   {[]byte("11:12 13")},
   371  				mountDir + "/blkio/mockpods/clean/blkio.throttle.write_bps_device":  {[]byte("21:22 23")},
   372  				mountDir + "/blkio/mockpods/clean/blkio.throttle.read_iops_device":  {[]byte("31:32 33")},
   373  				mountDir + "/blkio/mockpods/clean/blkio.throttle.write_iops_device": {[]byte("41:42 43")},
   374  			},
   375  		},
   376  		{
   377  			name:     "write empty struct",
   378  			fsi:      NewFsiMock(fsBlkioUtFiles),
   379  			cntnrDir: "/mockpods/clean",
   380  			blockIO:  BlockIOParameters{},
   381  			expectedFsWrites: map[string][][]byte{
   382  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight": {[]byte("0")},
   383  			},
   384  		},
   385  		{
   386  			name:     "multidevice weight and throttling, no weight write on -1",
   387  			fsi:      NewFsiMock(fsBlkioUtFiles),
   388  			cntnrDir: "/mockpods/clean",
   389  			blockIO: BlockIOParameters{
   390  				Weight:                  -1,
   391  				WeightDevice:            DeviceWeights{{1, 2, 3}, {4, 5, 6}},
   392  				ThrottleReadBpsDevice:   DeviceRates{{11, 12, 13}, {111, 112, 113}},
   393  				ThrottleWriteBpsDevice:  DeviceRates{{21, 22, 23}, {221, 222, 223}},
   394  				ThrottleReadIOPSDevice:  DeviceRates{{31, 32, 33}, {331, 332, 333}},
   395  				ThrottleWriteIOPSDevice: DeviceRates{{41, 42, 43}, {441, 442, 443}},
   396  			},
   397  			expectedFsWrites: map[string][][]byte{
   398  				mountDir + "/blkio/mockpods/clean/blkio.bfq.weight_device":          {[]byte("1:2 3"), []byte("4:5 6")},
   399  				mountDir + "/blkio/mockpods/clean/blkio.throttle.read_bps_device":   {[]byte("11:12 13"), []byte("111:112 113")},
   400  				mountDir + "/blkio/mockpods/clean/blkio.throttle.write_bps_device":  {[]byte("21:22 23"), []byte("221:222 223")},
   401  				mountDir + "/blkio/mockpods/clean/blkio.throttle.read_iops_device":  {[]byte("31:32 33"), []byte("331:332 333")},
   402  				mountDir + "/blkio/mockpods/clean/blkio.throttle.write_iops_device": {[]byte("41:42 43"), []byte("441:442 443")},
   403  			},
   404  		},
   405  		{
   406  			name:       "no bfq.weight",
   407  			fsi:        NewFsiMock(fsBlkioUtFiles),
   408  			cntnrDir:   "/mockpods/no-blkio-bfq-weight",
   409  			blockIO:    BlockIOParameters{Weight: 100},
   410  			writesFail: 1,
   411  			expectedFsWrites: map[string][][]byte{
   412  				mountDir + "/blkio/mockpods/no-blkio-bfq-weight/blkio.weight": {[]byte("100")},
   413  			},
   414  		},
   415  		{
   416  			name:     "all writes fail",
   417  			fsi:      NewFsiMock(fsBlkioUtFiles),
   418  			cntnrDir: "write-enodev",
   419  			blockIO: BlockIOParameters{
   420  				Weight:                -1,
   421  				WeightDevice:          DeviceWeights{{1, 0, 100}},
   422  				ThrottleReadBpsDevice: DeviceRates{{11, 12, 13}},
   423  			},
   424  			expectedErrorCount: 2,
   425  			expectedErrorSubstrings: []string{
   426  				"\"1:0 100\"",
   427  				"\"11:12 13\"",
   428  				"\"blkio.bfq.weight_device\"",
   429  				"\"blkio.weight_device\"",
   430  				"read_bps_device",
   431  				"no such device",
   432  				"file not found",
   433  			},
   434  		},
   435  		{
   436  			name:     "all files missing",
   437  			fsi:      NewFsiMock(fsBlkioUtFiles),
   438  			cntnrDir: "/this/container/does/not/exist",
   439  			blockIO: BlockIOParameters{
   440  				Weight:                  10,
   441  				WeightDevice:            DeviceWeights{{Major: 1, Minor: 2, Weight: 3}},
   442  				ThrottleReadBpsDevice:   DeviceRates{{Major: 11, Minor: 12, Rate: 13}},
   443  				ThrottleWriteBpsDevice:  DeviceRates{{Major: 21, Minor: 22, Rate: 23}},
   444  				ThrottleReadIOPSDevice:  DeviceRates{{Major: 31, Minor: 32, Rate: 33}},
   445  				ThrottleWriteIOPSDevice: DeviceRates{{Major: 41, Minor: 42, Rate: 43}},
   446  			},
   447  			expectedErrorCount: 6,
   448  			expectedErrorSubstrings: []string{
   449  				"file not found",
   450  				"blkio.bfq.weight",
   451  				"blkio.bfq.weight_device",
   452  				"blkio.throttle.read_bps_device",
   453  				"blkio.throttle.write_bps_device",
   454  				"blkio.throttle.read_iops_device",
   455  				"blkio.throttle.write_iops_device",
   456  			},
   457  		},
   458  	}
   459  	for _, tc := range tcases {
   460  		t.Run(tc.name, func(t *testing.T) {
   461  			fsi = tc.fsi
   462  			overrideFsFuncs(fsi.(*fsMock), tc.fsFuncs)
   463  			err := SetBlkioParameters(tc.cntnrDir, tc.blockIO)
   464  			testutils.VerifyError(t, err, tc.expectedErrorCount, tc.expectedErrorSubstrings)
   465  			validateWriteHistory(t, tc.expectedFsWrites, fsi.(*fsMock).files)
   466  		})
   467  	}
   468  }