From 2f9debe35b28f04fae5e642e182e088bdf5c48ca Mon Sep 17 00:00:00 2001 From: Ed Bartosh Date: Wed, 11 Jul 2018 16:13:02 +0300 Subject: [PATCH] fpga_crihook: check if bitstream is already programmed FPGA device can be already programmed with requested bitstream. In this case hook should not programm the device again. --- cmd/fpga_crihook/main.go | 126 ++++++++++---- cmd/fpga_crihook/main_test.go | 158 ++++++++++++------ .../testdata/config-no-FPGA-devices.json | 136 +++++++++++++++ .../testdata/config-no-devices.json | 126 ++++++++++++++ .../testdata/config-no-linux.json | 88 ++++++++++ .../afu_id_already_programmed | 1 + .../afu_id_not_programmed_yet | 1 + 7 files changed, 550 insertions(+), 86 deletions(-) create mode 100644 cmd/fpga_crihook/testdata/config-no-FPGA-devices.json create mode 100644 cmd/fpga_crihook/testdata/config-no-devices.json create mode 100644 cmd/fpga_crihook/testdata/config-no-linux.json create mode 100644 cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_already_programmed create mode 100644 cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_not_programmed_yet diff --git a/cmd/fpga_crihook/main.go b/cmd/fpga_crihook/main.go index 558b537e..9d5851a0 100644 --- a/cmd/fpga_crihook/main.go +++ b/cmd/fpga_crihook/main.go @@ -20,8 +20,10 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" "path" + "regexp" "strings" "github.com/golang/glog" @@ -34,6 +36,8 @@ const ( fpgaRegionEnv = "FPGA_REGION" fpgaAfuEnv = "FPGA_AFU" fpgaBitStreamExt = ".gbs" + fpgaDevRegexp = `\/dev\/intel-fpga-port.(\d)$` + afuIDTemplate = "/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id" ) func decodeJSONStream(reader io.Reader) (map[string]interface{}, error) { @@ -44,16 +48,24 @@ func decodeJSONStream(reader io.Reader) (map[string]interface{}, error) { } type hookEnv struct { - bitStreamDir string - config string - execer utilsexec.Interface + bitStreamDir string + config string + execer utilsexec.Interface + afuIDTemplate string } -func newHookEnv(bitStreamDir string, config string, execer utilsexec.Interface) *hookEnv { +type fpgaParams struct { + region string + afu string + devNum string +} + +func newHookEnv(bitStreamDir string, config string, execer utilsexec.Interface, afuIDTemplate string) *hookEnv { return &hookEnv{ bitStreamDir, config, execer, + afuIDTemplate, } } @@ -61,37 +73,37 @@ func canonize(uuid string) string { return strings.ToLower(strings.Replace(uuid, "-", "", -1)) } -func (he *hookEnv) getFPGAParams(reader io.Reader) (string, string, error) { +func (he *hookEnv) getFPGAParams(reader io.Reader) (*fpgaParams, error) { content, err := decodeJSONStream(reader) if err != nil { - return "", "", err + return nil, err } bundle, ok := content["bundle"] if !ok { - return "", "", fmt.Errorf("no 'bundle' field in the configuration") + return nil, fmt.Errorf("no 'bundle' field in the configuration") } configPath := path.Join(fmt.Sprint(bundle), he.config) configFile, err := os.Open(configPath) if err != nil { - return "", "", err + return nil, err } defer configFile.Close() content, err = decodeJSONStream(configFile) if err != nil { - return "", "", fmt.Errorf("can't decode %s", configPath) + return nil, fmt.Errorf("can't decode %s", configPath) } process, ok := content["process"] if !ok { - return "", "", fmt.Errorf("no 'process' field found in %s", configPath) + return nil, fmt.Errorf("no 'process' field found in %s", configPath) } rawEnv, ok := process.(map[string]interface{})["env"] if !ok { - return "", "", fmt.Errorf("no 'env' field found in the 'process' struct in %s", configPath) + return nil, fmt.Errorf("no 'env' field found in the 'process' struct in %s", configPath) } dEnv := make(map[string]string) @@ -102,85 +114,125 @@ func (he *hookEnv) getFPGAParams(reader io.Reader) (string, string, error) { fpgaRegion, ok := dEnv[fpgaRegionEnv] if !ok { - return "", "", fmt.Errorf("%s environment is not set in the 'process/env' list in %s", fpgaRegionEnv, configPath) + return nil, fmt.Errorf("%s environment is not set in the 'process/env' list in %s", fpgaRegionEnv, configPath) } fpgaAfu, ok := dEnv[fpgaAfuEnv] if !ok { - return fpgaRegion, "", fmt.Errorf("%s environment is not set in the 'process/env' list in %s", fpgaAfuEnv, configPath) + return nil, fmt.Errorf("%s environment is not set in the 'process/env' list in %s", fpgaAfuEnv, configPath) } - return canonize(fpgaRegion), canonize(fpgaAfu), nil + linux, ok := content["linux"] + if !ok { + return nil, fmt.Errorf("no 'linux' field found in %s", configPath) + } + + rawDevices, ok := linux.(map[string]interface{})["devices"] + if !ok { + return nil, fmt.Errorf("no 'devices' field found in the 'linux' struct in %s", configPath) + } + + pattern := regexp.MustCompile(fpgaDevRegexp) + for _, device := range rawDevices.([]interface{}) { + deviceNum := pattern.FindStringSubmatch(device.(map[string]interface{})["path"].(string)) + if deviceNum != nil { + return &fpgaParams{region: canonize(fpgaRegion), afu: canonize(fpgaAfu), devNum: deviceNum[1]}, nil + } + } + + return nil, fmt.Errorf("no FPGA devices found in linux/devices list in %s", configPath) + } -func (he *hookEnv) validateBitStream(fpgaRegion string, fpgaAfu string, fpgaBitStreamPath string) error { +func (he *hookEnv) validateBitStream(params *fpgaParams, fpgaBitStreamPath string) error { output, err := he.execer.Command("packager", "gbs-info", "--gbs", fpgaBitStreamPath).CombinedOutput() if err != nil { - return fmt.Errorf("%s/%s: can't get bitstream info: %v", fpgaRegion, fpgaAfu, err) + return fmt.Errorf("%s/%s: can't get bitstream info: %v", params.region, params.afu, err) } reader := bytes.NewBuffer(output) content, err := decodeJSONStream(reader) if err != nil { - return fmt.Errorf("%s/%s: can't decode 'packager gbs-info' output: %v", fpgaRegion, fpgaAfu, err) + return fmt.Errorf("%s/%s: can't decode 'packager gbs-info' output: %v", params.region, params.afu, err) } afuImage, ok := content["afu-image"] if !ok { - return fmt.Errorf("%s/%s: 'afu-image' field not found in the 'packager gbs-info' output", fpgaRegion, fpgaAfu) + return fmt.Errorf("%s/%s: 'afu-image' field not found in the 'packager gbs-info' output", params.region, params.afu) } interfaceUUID, ok := afuImage.(map[string]interface{})["interface-uuid"] if !ok { - return fmt.Errorf("%s/%s: 'interface-uuid' field not found in the 'packager gbs-info' output", fpgaRegion, fpgaAfu) + return fmt.Errorf("%s/%s: 'interface-uuid' field not found in the 'packager gbs-info' output", params.region, params.afu) } acceleratorClusters, ok := afuImage.(map[string]interface{})["accelerator-clusters"] if !ok { - return fmt.Errorf("%s/%s: 'accelerator-clusters' field not found in the 'packager gbs-info' output", fpgaRegion, fpgaAfu) + return fmt.Errorf("%s/%s: 'accelerator-clusters' field not found in the 'packager gbs-info' output", params.region, params.afu) } - if canonize(interfaceUUID.(string)) != canonize(fpgaRegion) { - return fmt.Errorf("bitstream is not for this device: region(%s) and interface-uuid(%s) don't match", fpgaRegion, interfaceUUID) + if canonize(interfaceUUID.(string)) != params.region { + return fmt.Errorf("bitstream is not for this device: region(%s) and interface-uuid(%s) don't match", params.region, interfaceUUID) } acceleratorTypeUUID, ok := acceleratorClusters.([]interface{})[0].(map[string]interface{})["accelerator-type-uuid"] if !ok { - return fmt.Errorf("%s/%s: 'accelerator-type-uuid' field not found in the 'packager gbs-info' output", fpgaRegion, fpgaAfu) + return fmt.Errorf("%s/%s: 'accelerator-type-uuid' field not found in the 'packager gbs-info' output", params.region, params.afu) } - if canonize(acceleratorTypeUUID.(string)) != canonize(fpgaAfu) { - return fmt.Errorf("incorrect bitstream: AFU(%s) and accelerator-type-uuid(%s) don't match", fpgaAfu, acceleratorTypeUUID) + if canonize(acceleratorTypeUUID.(string)) != params.afu { + return fmt.Errorf("incorrect bitstream: AFU(%s) and accelerator-type-uuid(%s) don't match", params.afu, acceleratorTypeUUID) } return nil } -func (he *hookEnv) programBitStream(fpgaRegion string, fpgaAfu string, fpgaBitStreamPath string) error { +func (he *hookEnv) programBitStream(params *fpgaParams, fpgaBitStreamPath string) error { output, err := he.execer.Command("fpgaconf", fpgaBitStreamPath).CombinedOutput() if err != nil { - return fmt.Errorf("failed to program AFU %s to region %s: error: %v, output: %s", fpgaAfu, fpgaRegion, err, string(output)) + return fmt.Errorf("failed to program AFU %s to region %s: error: %v, output: %s", params.afu, params.region, err, string(output)) } return nil } +func (he *hookEnv) getProgrammedAfu(deviceNum string) (string, error) { + // NOTE: only one region per device is supported, hence + // deviceNum is used twice (device and port numbers are the same) + afuIDPath := fmt.Sprintf(he.afuIDTemplate, deviceNum, deviceNum) + data, err := ioutil.ReadFile(afuIDPath) + if err != nil { + return "", err + } + return strings.TrimSpace(string(data)), nil +} + func (he *hookEnv) process(reader io.Reader) error { - fpgaRegion, fpgaAfu, err := he.getFPGAParams(reader) + params, err := he.getFPGAParams(reader) if err != nil { - return fmt.Errorf("couldn't get FPGA region and AFU: %v, skipping", err) + return fmt.Errorf("couldn't get FPGA region, AFU and device number: %v, skipping", err) } - fpgaBitStreamPath := path.Join(he.bitStreamDir, fpgaRegion, fpgaAfu+fpgaBitStreamExt) - if _, err = os.Stat(fpgaBitStreamPath); os.IsNotExist(err) { - return fmt.Errorf("%s/%s: bitstream is not found", fpgaRegion, fpgaAfu) - } - - err = he.validateBitStream(fpgaRegion, fpgaAfu, fpgaBitStreamPath) + programmedAfu, err := he.getProgrammedAfu(params.devNum) if err != nil { return err } - err = he.programBitStream(fpgaRegion, fpgaAfu, fpgaBitStreamPath) + if canonize(programmedAfu) == params.afu { + // Afu is already programmed + return nil + } + + fpgaBitStreamPath := path.Join(he.bitStreamDir, params.region, params.afu+fpgaBitStreamExt) + if _, err = os.Stat(fpgaBitStreamPath); os.IsNotExist(err) { + return fmt.Errorf("%s/%s: bitstream is not found", params.region, params.afu) + } + + err = he.validateBitStream(params, fpgaBitStreamPath) + if err != nil { + return err + } + + err = he.programBitStream(params, fpgaBitStreamPath) if err != nil { return err } @@ -196,7 +248,7 @@ func main() { os.Setenv("PATH", "/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin") } - he := newHookEnv(fpgaBitStreamDirectory, configJSON, utilsexec.New()) + he := newHookEnv(fpgaBitStreamDirectory, configJSON, utilsexec.New(), afuIDTemplate) err := he.process(os.Stdin) if err != nil { diff --git a/cmd/fpga_crihook/main_test.go b/cmd/fpga_crihook/main_test.go index a6bb5c4c..803a3316 100644 --- a/cmd/fpga_crihook/main_test.go +++ b/cmd/fpga_crihook/main_test.go @@ -26,18 +26,21 @@ import ( func TestGetFPGAParams(t *testing.T) { tcases := []struct { - stdinJSON string - configJSON string - expectedErr bool - expectedRegion string - expectedAFU string + stdinJSON string + configJSON string + afuIDPath string + expectedErr bool + expectedRegion string + expectedAFU string + expectedDeviceNum string }{ { - stdinJSON: "stdin-correct.json", - configJSON: "config-correct.json", - expectedErr: false, - expectedRegion: "ce48969398f05f33946d560708be108a", - expectedAFU: "f7df405cbd7acf7222f144b0b93acd18", + stdinJSON: "stdin-correct.json", + configJSON: "config-correct.json", + expectedErr: false, + expectedRegion: "ce48969398f05f33946d560708be108a", + expectedAFU: "f7df405cbd7acf7222f144b0b93acd18", + expectedDeviceNum: "0", }, { stdinJSON: "stdin-broken-json.json", @@ -80,6 +83,27 @@ func TestGetFPGAParams(t *testing.T) { expectedErr: true, expectedRegion: "ce48969398f05f33946d560708be108a", }, + { + stdinJSON: "stdin-correct.json", + configJSON: "config-no-linux.json", + expectedErr: true, + expectedRegion: "ce48969398f05f33946d560708be108a", + expectedAFU: "f7df405cbd7acf7222f144b0b93acd18", + }, + { + stdinJSON: "stdin-correct.json", + configJSON: "config-no-devices.json", + expectedErr: true, + expectedRegion: "ce48969398f05f33946d560708be108a", + expectedAFU: "f7df405cbd7acf7222f144b0b93acd18", + }, + { + stdinJSON: "stdin-correct.json", + configJSON: "config-no-FPGA-devices.json", + expectedErr: true, + expectedRegion: "ce48969398f05f33946d560708be108a", + expectedAFU: "f7df405cbd7acf7222f144b0b93acd18", + }, } for _, tc := range tcases { stdin, err := os.Open(path.Join("testdata", tc.stdinJSON)) @@ -87,16 +111,22 @@ func TestGetFPGAParams(t *testing.T) { t.Fatalf("can't open file %s: %v", tc.stdinJSON, err) } - he := newHookEnv("", tc.configJSON, nil) + he := newHookEnv("", tc.configJSON, nil, "") - region, afu, err := he.getFPGAParams(stdin) + params, err := he.getFPGAParams(stdin) - if err != nil && !tc.expectedErr { - t.Errorf("unexpected error: %v", err) - } else if region != tc.expectedRegion { - t.Errorf("expected region: %s, actual: %s", tc.expectedRegion, region) - } else if afu != tc.expectedAFU { - t.Errorf("expected AFU: %s, actual: %s", tc.expectedAFU, afu) + if err != nil { + if !tc.expectedErr { + t.Errorf("unexpected error: %v", err) + } + } else { + if params.region != tc.expectedRegion { + t.Errorf("expected region: %s, actual: %s", tc.expectedRegion, params.region) + } else if params.afu != tc.expectedAFU { + t.Errorf("expected AFU: %s, actual: %s", tc.expectedAFU, params.afu) + } else if params.devNum != tc.expectedDeviceNum { + t.Errorf("expected device number: %s, actual: %s", tc.expectedDeviceNum, params.devNum) + } } } } @@ -113,14 +143,14 @@ func genFakeActions(fcmd *fakeexec.FakeCmd, num int) []fakeexec.FakeCommandActio func TestValidateBitstream(t *testing.T) { tcases := []struct { - fpgaRegion string - fpgaAfu string + params *fpgaParams expectedErr bool fakeAction []fakeexec.FakeCombinedOutputAction }{ { - fpgaRegion: "ce48969398f05f33946d560708be108a", - fpgaAfu: "f7df405cbd7acf7222f144b0b93acd18", + params: &fpgaParams{ + region: "ce48969398f05f33946d560708be108a", + afu: "f7df405cbd7acf7222f144b0b93acd18"}, expectedErr: false, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -129,12 +159,14 @@ func TestValidateBitstream(t *testing.T) { }, }, { + params: &fpgaParams{region: "", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 1} }, }, }, { + params: &fpgaParams{region: "", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -143,6 +175,7 @@ func TestValidateBitstream(t *testing.T) { }, }, { + params: &fpgaParams{region: "", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -151,6 +184,7 @@ func TestValidateBitstream(t *testing.T) { }, }, { + params: &fpgaParams{region: "", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -159,6 +193,7 @@ func TestValidateBitstream(t *testing.T) { }, }, { + params: &fpgaParams{region: "", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -167,7 +202,7 @@ func TestValidateBitstream(t *testing.T) { }, }, { - fpgaRegion: "this should not match", + params: &fpgaParams{region: "this should not match", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -176,7 +211,7 @@ func TestValidateBitstream(t *testing.T) { }, }, { - fpgaRegion: "ce48969398f05f33946d560708be108a", + params: &fpgaParams{region: "ce48969398f05f33946d560708be108a", afu: ""}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -185,8 +220,9 @@ func TestValidateBitstream(t *testing.T) { }, }, { - fpgaRegion: "ce48969398f05f33946d560708be108a", - fpgaAfu: "this should not match", + params: &fpgaParams{ + region: "ce48969398f05f33946d560708be108a", + afu: "this should not match"}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { @@ -199,8 +235,8 @@ func TestValidateBitstream(t *testing.T) { for _, tc := range tcases { fcmd := fakeexec.FakeCmd{CombinedOutputScript: tc.fakeAction} execer := fakeexec.FakeExec{CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript))} - he := newHookEnv("", "", &execer) - err := he.validateBitStream(tc.fpgaRegion, tc.fpgaAfu, "") + he := newHookEnv("", "", &execer, "") + err := he.validateBitStream(tc.params, "") if err != nil && !tc.expectedErr { t.Errorf("unexpected error: %v", err) } @@ -209,22 +245,23 @@ func TestValidateBitstream(t *testing.T) { func TestProgramBitStream(t *testing.T) { tcases := []struct { - fpgaRegion string - fpgaAfu string + params *fpgaParams expectedErr bool fakeAction []fakeexec.FakeCombinedOutputAction }{ { - fpgaRegion: "ce48969398f05f33946d560708be108a", - fpgaAfu: "f7df405cbd7acf7222f144b0b93acd18", + params: &fpgaParams{ + region: "ce48969398f05f33946d560708be108a", + afu: "f7df405cbd7acf7222f144b0b93acd18"}, expectedErr: false, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return []byte(""), nil }, }, }, { - fpgaRegion: "ce48969398f05f33946d560708be108a", - fpgaAfu: "f7df405cbd7acf7222f144b0b93acd18", + params: &fpgaParams{ + region: "ce48969398f05f33946d560708be108a", + afu: "f7df405cbd7acf7222f144b0b93acd18"}, expectedErr: true, fakeAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} }, @@ -235,8 +272,8 @@ func TestProgramBitStream(t *testing.T) { for _, tc := range tcases { fcmd := fakeexec.FakeCmd{CombinedOutputScript: tc.fakeAction} execer := fakeexec.FakeExec{CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript))} - he := newHookEnv("", "", &execer) - err := he.programBitStream(tc.fpgaRegion, tc.fpgaAfu, "") + he := newHookEnv("", "", &execer, "") + err := he.programBitStream(tc.params, "") if err != nil && !tc.expectedErr { t.Errorf("unexpected error: %v", err) } @@ -247,13 +284,15 @@ func TestProcess(t *testing.T) { tcases := []struct { stdinJSON string configJSON string + afuIDTemplate string expectedErr bool fakeCombinedOutputAction []fakeexec.FakeCombinedOutputAction }{ { - stdinJSON: "stdin-correct.json", - configJSON: "config-correct.json", - expectedErr: false, + stdinJSON: "stdin-correct.json", + configJSON: "config-correct.json", + afuIDTemplate: "testdata/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id_already_programmed", + expectedErr: false, fakeCombinedOutputAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return ioutil.ReadFile("testdata/gbs-info-correct.json") @@ -262,19 +301,39 @@ func TestProcess(t *testing.T) { }, }, { - stdinJSON: "stdin-correct.json", - configJSON: "config-no-afu.json", - expectedErr: true, + stdinJSON: "stdin-correct.json", + configJSON: "config-correct.json", + afuIDTemplate: "testdata/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id_not_programmed_yet", + expectedErr: false, + fakeCombinedOutputAction: []fakeexec.FakeCombinedOutputAction{ + func() ([]byte, error) { + return ioutil.ReadFile("testdata/gbs-info-correct.json") + }, + func() ([]byte, error) { return []byte(""), nil }, + }, }, { - stdinJSON: "stdin-correct.json", - configJSON: "config-non-existing-bitstream.json", - expectedErr: true, + stdinJSON: "stdin-correct.json", + configJSON: "config-no-afu.json", + afuIDTemplate: "testdata/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id_not_programmed_yet", + expectedErr: true, }, { stdinJSON: "stdin-correct.json", configJSON: "config-correct.json", expectedErr: true, + }, + { + stdinJSON: "stdin-correct.json", + configJSON: "config-non-existing-bitstream.json", + afuIDTemplate: "testdata/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id_not_programmed_yet", + expectedErr: true, + }, + { + stdinJSON: "stdin-correct.json", + configJSON: "config-correct.json", + afuIDTemplate: "testdata/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id_not_programmed_yet", + expectedErr: true, fakeCombinedOutputAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return ioutil.ReadFile("testdata/gbs-info-no-accelerator-type-uuid.json") @@ -282,9 +341,10 @@ func TestProcess(t *testing.T) { }, }, { - stdinJSON: "stdin-correct.json", - configJSON: "config-correct.json", - expectedErr: true, + stdinJSON: "stdin-correct.json", + configJSON: "config-correct.json", + afuIDTemplate: "testdata/sys/class/fpga/intel-fpga-dev.%s/intel-fpga-port.%s/afu_id_not_programmed_yet", + expectedErr: true, fakeCombinedOutputAction: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return ioutil.ReadFile("testdata/gbs-info-correct.json") @@ -303,7 +363,7 @@ func TestProcess(t *testing.T) { fcmd := fakeexec.FakeCmd{CombinedOutputScript: tc.fakeCombinedOutputAction} execer := fakeexec.FakeExec{CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript))} - he := newHookEnv("testdata/intel.com/fpga", tc.configJSON, &execer) + he := newHookEnv("testdata/intel.com/fpga", tc.configJSON, &execer, tc.afuIDTemplate) err = he.process(stdin) if err != nil && !tc.expectedErr { diff --git a/cmd/fpga_crihook/testdata/config-no-FPGA-devices.json b/cmd/fpga_crihook/testdata/config-no-FPGA-devices.json new file mode 100644 index 00000000..1b60807c --- /dev/null +++ b/cmd/fpga_crihook/testdata/config-no-FPGA-devices.json @@ -0,0 +1,136 @@ +{ + "ociVersion": "1.0.0", + "process": { + "user": { + "uid": 0, + "gid": 0 + }, + "args": [ + "sh", + "/usr/bin/test_fpga.sh" + ], + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + "HOSTNAME=test-fpga-region", + "FPGA_REGION=ce48969398f05f33946d560708be108a", + "FPGA_AFU=f7df405cbd7acf7222f144b0b93acd18", + "KUBERNETES_SERVICE_PORT=443", + "KUBERNETES_PORT_443_TCP_PROTO=tcp", + "OPAE_URL=https://github.com/OPAE/opae-sdk/releases/download/1.0.0-5" + ], + "oomScoreAdj": 999 + }, + "hostname": "test-fpga-region", + "mounts": [ + { + "destination": "/proc", + "type": "proc", + "source": "proc" + }, + { + "destination": "/dev", + "type": "tmpfs", + "source": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/sys", + "type": "sysfs", + "source": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + } + ], + "hooks": { + "prestart": [ + { + "path": "/usr/local/bin/fpga_crihook", + "args": [ + "/usr/local/bin/fpga_crihook" + ], + "env": [ + "stage=prestart" + ] + } + ] + }, + "annotations": { + "io.kubernetes.container.hash": "22e3c9bc", + "io.kubernetes.container.name": "test-container", + "io.kubernetes.container.restartCount": "0", + "io.kubernetes.container.terminationMessagePolicy": "File", + "io.kubernetes.cri-o.ContainerID": "a15ee43034ade4083279bdbb7cf656a971809cfb00eb97a7751bb46dd74c9150", + "io.kubernetes.cri-o.ContainerType": "container", + "io.kubernetes.cri-o.Created": "2018-06-20T15:08:41.361981842+03:00", + "io.kubernetes.cri-o.LogPath": "/var/log/pods/a869ac70-7482-11e8-a041-c81f66f62fcc/test-container/0.log", + "io.kubernetes.cri-o.Metadata": "{\"name\":\"test-container\"}", + "io.kubernetes.cri-o.SandboxID": "a30829b039e1c631432fe7ab5c748a393c105ed90ec42a6cc95bd7d33356b94e", + "io.kubernetes.cri-o.SandboxName": "k8s_POD_test-fpga-region_default_a869ac70-7482-11e8-a041-c81f66f62fcc_0", + "io.kubernetes.cri-o.SeccompProfilePath": "", + "io.kubernetes.cri-o.Stdin": "false", + "io.kubernetes.cri-o.StdinOnce": "false", + "io.kubernetes.cri-o.TTY": "false", + "io.kubernetes.pod.name": "test-fpga-region", + "io.kubernetes.pod.namespace": "default", + "io.kubernetes.pod.terminationGracePeriod": "30", + "io.kubernetes.pod.uid": "a869ac70-7482-11e8-a041-c81f66f62fcc" + }, + "linux": { + "resources": { + "devices": [ + { + "allow": false, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 243, + "minor": 0, + "access": "mrw" + } + ], + "memory": { + "limit": 0 + }, + "cpu": { + "shares": 1024, + "quota": 100000, + "period": 100000 + }, + "pids": { + "limit": 1024 + } + }, + "cgroupsPath": "/kubepods/burstable/poda869ac70-7482-11e8-a041-c81f66f62fcc/crio-a15ee43034ade4083279bdbb7cf656a971809cfb00eb97a7751bb46dd74c9150", + "namespaces": [ + { + "type": "pid" + }, + { + "type": "mount" + } + ], + "devices": [ + { + "path": "/dev/not-FPGA-device", + "type": "c", + "major": 243, + "minor": 0, + "uid": 0, + "gid": 0 + } + ], + "rootfsPropagation": "rslave" + } +} diff --git a/cmd/fpga_crihook/testdata/config-no-devices.json b/cmd/fpga_crihook/testdata/config-no-devices.json new file mode 100644 index 00000000..a87f537c --- /dev/null +++ b/cmd/fpga_crihook/testdata/config-no-devices.json @@ -0,0 +1,126 @@ +{ + "ociVersion": "1.0.0", + "process": { + "user": { + "uid": 0, + "gid": 0 + }, + "args": [ + "sh", + "/usr/bin/test_fpga.sh" + ], + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + "HOSTNAME=test-fpga-region", + "FPGA_REGION=ce48969398f05f33946d560708be108a", + "FPGA_AFU=f7df405cbd7acf7222f144b0b93acd18", + "KUBERNETES_SERVICE_PORT=443", + "KUBERNETES_PORT_443_TCP_PROTO=tcp", + "OPAE_URL=https://github.com/OPAE/opae-sdk/releases/download/1.0.0-5" + ], + "oomScoreAdj": 999 + }, + "hostname": "test-fpga-region", + "mounts": [ + { + "destination": "/proc", + "type": "proc", + "source": "proc" + }, + { + "destination": "/dev", + "type": "tmpfs", + "source": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/sys", + "type": "sysfs", + "source": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + } + ], + "hooks": { + "prestart": [ + { + "path": "/usr/local/bin/fpga_crihook", + "args": [ + "/usr/local/bin/fpga_crihook" + ], + "env": [ + "stage=prestart" + ] + } + ] + }, + "annotations": { + "io.kubernetes.container.hash": "22e3c9bc", + "io.kubernetes.container.name": "test-container", + "io.kubernetes.container.restartCount": "0", + "io.kubernetes.container.terminationMessagePolicy": "File", + "io.kubernetes.cri-o.ContainerID": "a15ee43034ade4083279bdbb7cf656a971809cfb00eb97a7751bb46dd74c9150", + "io.kubernetes.cri-o.ContainerType": "container", + "io.kubernetes.cri-o.Created": "2018-06-20T15:08:41.361981842+03:00", + "io.kubernetes.cri-o.LogPath": "/var/log/pods/a869ac70-7482-11e8-a041-c81f66f62fcc/test-container/0.log", + "io.kubernetes.cri-o.Metadata": "{\"name\":\"test-container\"}", + "io.kubernetes.cri-o.SandboxID": "a30829b039e1c631432fe7ab5c748a393c105ed90ec42a6cc95bd7d33356b94e", + "io.kubernetes.cri-o.SandboxName": "k8s_POD_test-fpga-region_default_a869ac70-7482-11e8-a041-c81f66f62fcc_0", + "io.kubernetes.cri-o.SeccompProfilePath": "", + "io.kubernetes.cri-o.Stdin": "false", + "io.kubernetes.cri-o.StdinOnce": "false", + "io.kubernetes.cri-o.TTY": "false", + "io.kubernetes.pod.name": "test-fpga-region", + "io.kubernetes.pod.namespace": "default", + "io.kubernetes.pod.terminationGracePeriod": "30", + "io.kubernetes.pod.uid": "a869ac70-7482-11e8-a041-c81f66f62fcc" + }, + "linux": { + "resources": { + "devices": [ + { + "allow": false, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 243, + "minor": 0, + "access": "mrw" + } + ], + "memory": { + "limit": 0 + }, + "cpu": { + "shares": 1024, + "quota": 100000, + "period": 100000 + }, + "pids": { + "limit": 1024 + } + }, + "cgroupsPath": "/kubepods/burstable/poda869ac70-7482-11e8-a041-c81f66f62fcc/crio-a15ee43034ade4083279bdbb7cf656a971809cfb00eb97a7751bb46dd74c9150", + "namespaces": [ + { + "type": "pid" + }, + { + "type": "mount" + } + ], + "rootfsPropagation": "rslave" + } +} diff --git a/cmd/fpga_crihook/testdata/config-no-linux.json b/cmd/fpga_crihook/testdata/config-no-linux.json new file mode 100644 index 00000000..9b6af4b8 --- /dev/null +++ b/cmd/fpga_crihook/testdata/config-no-linux.json @@ -0,0 +1,88 @@ +{ + "ociVersion": "1.0.0", + "process": { + "user": { + "uid": 0, + "gid": 0 + }, + "args": [ + "sh", + "/usr/bin/test_fpga.sh" + ], + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + "HOSTNAME=test-fpga-region", + "FPGA_REGION=ce48969398f05f33946d560708be108a", + "FPGA_AFU=f7df405cbd7acf7222f144b0b93acd18", + "KUBERNETES_SERVICE_PORT=443", + "KUBERNETES_PORT_443_TCP_PROTO=tcp", + "OPAE_URL=https://github.com/OPAE/opae-sdk/releases/download/1.0.0-5" + ], + "oomScoreAdj": 999 + }, + "hostname": "test-fpga-region", + "mounts": [ + { + "destination": "/proc", + "type": "proc", + "source": "proc" + }, + { + "destination": "/dev", + "type": "tmpfs", + "source": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/sys", + "type": "sysfs", + "source": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + } + ], + "hooks": { + "prestart": [ + { + "path": "/usr/local/bin/fpga_crihook", + "args": [ + "/usr/local/bin/fpga_crihook" + ], + "env": [ + "stage=prestart" + ] + } + ] + }, + "annotations": { + "io.kubernetes.container.hash": "22e3c9bc", + "io.kubernetes.container.name": "test-container", + "io.kubernetes.container.restartCount": "0", + "io.kubernetes.container.terminationMessagePolicy": "File", + "io.kubernetes.cri-o.ContainerID": "a15ee43034ade4083279bdbb7cf656a971809cfb00eb97a7751bb46dd74c9150", + "io.kubernetes.cri-o.ContainerType": "container", + "io.kubernetes.cri-o.Created": "2018-06-20T15:08:41.361981842+03:00", + "io.kubernetes.cri-o.LogPath": "/var/log/pods/a869ac70-7482-11e8-a041-c81f66f62fcc/test-container/0.log", + "io.kubernetes.cri-o.Metadata": "{\"name\":\"test-container\"}", + "io.kubernetes.cri-o.SandboxID": "a30829b039e1c631432fe7ab5c748a393c105ed90ec42a6cc95bd7d33356b94e", + "io.kubernetes.cri-o.SandboxName": "k8s_POD_test-fpga-region_default_a869ac70-7482-11e8-a041-c81f66f62fcc_0", + "io.kubernetes.cri-o.SeccompProfilePath": "", + "io.kubernetes.cri-o.Stdin": "false", + "io.kubernetes.cri-o.StdinOnce": "false", + "io.kubernetes.cri-o.TTY": "false", + "io.kubernetes.pod.name": "test-fpga-region", + "io.kubernetes.pod.namespace": "default", + "io.kubernetes.pod.terminationGracePeriod": "30", + "io.kubernetes.pod.uid": "a869ac70-7482-11e8-a041-c81f66f62fcc" + } +} diff --git a/cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_already_programmed b/cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_already_programmed new file mode 100644 index 00000000..f9d63024 --- /dev/null +++ b/cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_already_programmed @@ -0,0 +1 @@ +f7df405cbd7acf7222f144b0b93acd18 diff --git a/cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_not_programmed_yet b/cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_not_programmed_yet new file mode 100644 index 00000000..157d528f --- /dev/null +++ b/cmd/fpga_crihook/testdata/sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id_not_programmed_yet @@ -0,0 +1 @@ +d8424dc4a4a3c413f89e433683f9040b