// Copyright 2018 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. //go:build kerneldrv // +build kerneldrv package kerneldrv import ( "errors" "flag" "fmt" "os" "path" "path/filepath" "reflect" "sort" "testing" "time" "k8s.io/klog/v2" pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" "k8s.io/utils/exec" fakeexec "k8s.io/utils/exec/testing" ) var errFake = errors.New("fake error") const ( adfCtlOutput = `Checking status of all devices. There is 3 QAT acceleration device(s) in the system: qat_dev0 - type: c6xx, inst_id: 0, node_id: 0, bsf: 0000:3b:00.0, #accel: 5 #engines: 10 state: up qat_dev1 - type: c6xx, inst_id: 1, node_id: 0, bsf: 0000:3d:00.0, #accel: 5 #engines: 10 state: up qat_dev2 - type: c6xx, inst_id: 2, node_id: 3, bsf: 0000:d8:00.0, #accel: 5 #engines: 10 state: up ` adfCtlOutputOneDown = `Checking status of all devices. There is 3 QAT acceleration device(s) in the system: qat_dev0 - type: c6xx, inst_id: 0, node_id: 0, bsf: 3b:00.0, #accel: 5 #engines: 10 state: up qat_dev1 - type: c6xx, inst_id: 1, node_id: 0, bsf: 3d:00.0, #accel: 5 #engines: 10 state: down qat_dev2 - type: c6xx, inst_id: 2, node_id: 3, bsf: d8:00.0, #accel: 5 #engines: 10 state: up ` adfCtlOutputVf = `Checking status of all devices. There is 7 QAT acceleration device(s) in the system: qat_dev0 - type: c6xx, inst_id: 0, node_id: 0, bsf: 0000:3b:00.0, #accel: 5 #engines: 10 state: up qat_dev1 - type: c6xx, inst_id: 1, node_id: 0, bsf: 0000:3b:00.0, #accel: 5 #engines: 10 state: up qat_dev2 - type: c6xx, inst_id: 2, node_id: 3, bsf: 0000:3b:00.0, #accel: 5 #engines: 10 state: up qat_dev3 - type: c6xxvf, inst_id: 0, node_id: 0, bsf: 0000:3b:01.0, #accel: 1 #engines: 1 state: up qat_dev4 - type: c6xxvf, inst_id: 1, node_id: 0, bsf: 0000:3b:01.1, #accel: 1 #engines: 1 state: up qat_dev5 - type: c6xxvf, inst_id: 2, node_id: 0, bsf: 0000:3b:01.2, #accel: 1 #engines: 1 state: up qat_dev6 - type: c6xxvf, inst_id: 3, node_id: 0, bsf: 0000:3b:01.3, #accel: 1 #engines: 1 state: up ` adfCtlOutputDenyListDevices = `Checking status of all devices. There is 7 QAT acceleration device(s) in the system: qat_dev0 - type: 4xxx, inst_id: 0, node_id: 0, bsf: 0000:3b:00.0, #accel: 5 #engines: 10 state: up qat_dev1 - type: 4xxx, inst_id: 1, node_id: 0, bsf: 0000:3c:00.0, #accel: 5 #engines: 10 state: up qat_dev2 - type: 4xxx, inst_id: 2, node_id: 3, bsf: 0000:3d:00.0, #accel: 5 #engines: 10 state: up qat_dev3 - type: 4xxxvf, inst_id: 0, node_id: 0, bsf: 0000:3b:01.0, #accel: 1 #engines: 1 state: up qat_dev4 - type: 4xxxvf, inst_id: 1, node_id: 0, bsf: 0000:3b:01.1, #accel: 1 #engines: 1 state: up qat_dev5 - type: 4xxxvf, inst_id: 2, node_id: 0, bsf: 0000:3b:01.2, #accel: 1 #engines: 1 state: up qat_dev6 - type: 4xxxvf, inst_id: 3, node_id: 0, bsf: 0000:3b:01.3, #accel: 1 #engines: 1 state: up ` ) func init() { _ = flag.Set("v", "4") //Enable debug output } func TestGetOnlineDevices(t *testing.T) { tcases := []struct { name string adfCtlError error adfCtlOutput string expectedDevNum int expectedErr bool iommuOn bool }{ { name: "all is good", adfCtlOutput: adfCtlOutput, expectedDevNum: 3, iommuOn: false, }, { name: "one device is down", adfCtlOutput: adfCtlOutputOneDown, expectedDevNum: 2, iommuOn: false, }, { name: "virtual functions enabled", adfCtlOutput: adfCtlOutputVf, expectedDevNum: 4, iommuOn: true, }, { name: "denylisted devices", adfCtlOutput: adfCtlOutputDenyListDevices, expectedDevNum: 0, iommuOn: true, }, { name: "adf_ctl fails to run", adfCtlError: errFake, expectedErr: true, }, } for _, tt := range tcases { t.Run(tt.name, func(t *testing.T) { fcmd := fakeexec.FakeCmd{ CombinedOutputScript: []fakeexec.FakeAction{ func() ([]byte, []byte, error) { return []byte(tt.adfCtlOutput), []byte{}, tt.adfCtlError }, }, } execer := fakeexec.FakeExec{ CommandScript: []fakeexec.FakeCommandAction{ func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, }, } dp := &DevicePlugin{ execer: &execer, } devices, err := dp.getOnlineDevices(tt.iommuOn) if tt.expectedErr && err == nil { t.Error("Expected error hasn't been triggered") } if !tt.expectedErr && err != nil { t.Errorf("Unexpected error: %+v", err) } if len(devices) != tt.expectedDevNum { t.Errorf("Wrong number of device detected: %d instead of %d", len(devices), tt.expectedDevNum) } }) } } func TestGetUIODevices(t *testing.T) { tcases := []struct { name string devType string bsf string uiodevs []string expectedErr bool }{ { name: "can't read sysfs", devType: "faketype", expectedErr: true, }, { name: "all is good", devType: "c6xx", uiodevs: []string{"uio0", "uio1"}, bsf: "da:00.0", }, } for tnum, tt := range tcases { t.Run(tt.name, func(t *testing.T) { var err error tmpdir := fmt.Sprintf("/tmp/qatplugin-getUIODevices-%d-%d", time.Now().Unix(), tnum) sysfs := filepath.Join(tmpdir, "sys") for _, uiodev := range tt.uiodevs { err = os.MkdirAll(filepath.Join(getUIODeviceListPath(sysfs, tt.devType, tt.bsf), uiodev), 0750) if err != nil { t.Fatal(err) } } devs, err := getUIODevices(sysfs, tt.devType, tt.bsf) if tt.expectedErr && err == nil { t.Error("Expected error hasn't been triggered") } if !tt.expectedErr && err != nil { t.Errorf("Unexpected error: %+v", err) } sort.Strings(tt.uiodevs) sort.Strings(devs) if tt.uiodevs != nil && !reflect.DeepEqual(devs, tt.uiodevs) { t.Error("Unexpected devices: ", devs) } err = os.RemoveAll(tmpdir) if err != nil { t.Fatal(err) } }) } } func TestParseConfigs(t *testing.T) { qatdevs := []device{ { id: "dev0", devtype: "c6xx", }, { id: "dev1", devtype: "c6xx", }, { id: "dev2", devtype: "c6xx", }, } tcases := []struct { name string testData string expectedErr bool }{ { name: "All is good", testData: "all_is_good", }, { name: "Missing section with LinitDevAccess=1", testData: "missing_pinned_section", expectedErr: true, }, { name: "Can't parse NumProcesses", testData: "cant_parse_num_processes", expectedErr: true, }, { name: "Inconsistent LimitDevAccess", testData: "inconsistent_limitdev", expectedErr: true, }, } for _, tt := range tcases { dp := &DevicePlugin{ configDir: "./test_data/" + tt.testData, } _, err := dp.parseConfigs(qatdevs) if tt.expectedErr && err == nil { t.Errorf("Test case '%s': expected error hasn't been triggered", tt.name) } if !tt.expectedErr && err != nil { t.Errorf("Test case '%s': Unexpected error: %+v", tt.name, err) } } } func TestGetDevTree(t *testing.T) { tmpdir := fmt.Sprintf("/tmp/qatplugin-getDevTree-%d", time.Now().Unix()) tcases := []struct { name string sysfs string config map[string]section uiodevs map[string][]string qatdevs []device expectedErr bool }{ { name: "All is good", sysfs: "sys", uiodevs: map[string][]string{ "da:00.0": {"uio4", "uio5"}, }, qatdevs: []device{ { id: "dev0", devtype: "c6xx", bsf: "da:00.0", }, }, config: map[string]section{ "TESTSHIM": { endpoints: []endpoint{ { id: "dev0", processes: 2, }, }, }, "TESTSHIM2": { endpoints: []endpoint{ { id: "dev0", processes: 2, }, }, }, "TESTPINNED": { endpoints: []endpoint{ { id: "dev0", processes: 2, }, }, pinned: true, }, }, }, { name: "Wrong devfs", sysfs: "wrongdev", qatdevs: []device{ { id: "dev0", devtype: "c6xx", bsf: "da:00.0", }, }, expectedErr: true, }, } for _, tt := range tcases { t.Run(tt.name, func(t *testing.T) { var err error sysfs := filepath.Join(tmpdir, "sys") err = os.MkdirAll(sysfs, 0750) if err != nil { t.Fatal(err) } for _, qatdev := range tt.qatdevs { for _, uiodev := range tt.uiodevs[qatdev.bsf] { err = os.MkdirAll(filepath.Join(getUIODeviceListPath(sysfs, qatdev.devtype, qatdev.bsf), uiodev), 0750) if err != nil { t.Fatal(err) } } } _, err = getDevTree(path.Join(tmpdir, tt.sysfs), tt.qatdevs, tt.config) if tt.expectedErr && err == nil { t.Errorf("Test case '%s': expected error hasn't been triggered", tt.name) } if !tt.expectedErr && err != nil { t.Errorf("Test case '%s': Unexpected error: %+v", tt.name, err) } err = os.RemoveAll(tmpdir) if err != nil { t.Fatal(err) } }) } } func TestPostAllocate(t *testing.T) { tcases := []struct { name string envs map[string]string expectedEnvs []string expectedErr bool }{ { name: "All is good", envs: map[string]string{ "SOMEVAR": "some value", "QAT_SECTION_NAME_cy1_dc0_15": "TESTSHIM", "QAT_SECTION_NAME_cy1_dc0_32": "TESTSHIM2", }, expectedEnvs: []string{ "SOMEVAR", "QAT_SECTION_NAME_cy1_dc0_0", "QAT_SECTION_NAME_cy1_dc0_1", }, }, { name: "Wrong env variable name format", envs: map[string]string{ "QAT_SECTION_NAME_JUSTWRONG": "some value", }, expectedErr: true, }, } for _, tc := range tcases { response := new(pluginapi.AllocateResponse) cresp := new(pluginapi.ContainerAllocateResponse) cresp.Envs = tc.envs response.ContainerResponses = append(response.ContainerResponses, cresp) dp := &DevicePlugin{} err := dp.PostAllocate(response) for _, key := range tc.expectedEnvs { if _, ok := cresp.Envs[key]; !ok { t.Errorf("Test case '%s': expcted env variable '%s' is missing", tc.name, key) } } if tc.expectedErr && err == nil { t.Errorf("Test case '%s': expected error hasn't been triggered", tc.name) } if !tc.expectedErr && err != nil { t.Errorf("Test case '%s': Unexpected error: %+v", tc.name, err) } klog.V(4).Info(response) } }