fpga: add mode CLI switch

By default the fpga plugin announce regions' interface IDs. With
added `-mode af` switch the plugins announces IDs of accelerator
functions instead of regions.
This commit is contained in:
Dmitry Rozhkov 2018-05-18 16:40:32 +03:00
parent 7e830d7953
commit 49840e9720
2 changed files with 185 additions and 16 deletions

View File

@ -39,12 +39,32 @@ const (
devfsDirectory = "/dev" devfsDirectory = "/dev"
deviceRE = `^intel-fpga-dev.[0-9]+$` deviceRE = `^intel-fpga-dev.[0-9]+$`
portRE = `^intel-fpga-port.[0-9]+$` portRE = `^intel-fpga-port.[0-9]+$`
fmeRE = `^intel-fpga-fme.[0-9]+$`
// Device plugin settings. // Device plugin settings.
pluginEndpointPrefix = "intel-fpga" pluginEndpointPrefix = "intel-fpga"
resourceNamePrefix = "intel.com/fpga" resourceNamePrefix = "intel.com/fpga"
) )
type pluginMode int
const (
wrongMode pluginMode = iota
afMode
regionMode
)
func parseMode(input string) pluginMode {
switch input {
case "af":
return afMode
case "region":
return regionMode
}
return wrongMode
}
// deviceManager manages Intel FPGA devices. // deviceManager manages Intel FPGA devices.
type deviceManager struct { type deviceManager struct {
srv deviceplugin.Server srv deviceplugin.Server
@ -52,21 +72,24 @@ type deviceManager struct {
name string name string
devices map[string]deviceplugin.DeviceInfo devices map[string]deviceplugin.DeviceInfo
root string root string
mode pluginMode
} }
func newDeviceManager(resourceName string, fpgaId string, rootDir string) *deviceManager { func newDeviceManager(resourceName string, fpgaId string, rootDir string, mode pluginMode) *deviceManager {
return &deviceManager{ return &deviceManager{
fpgaId: fpgaId, fpgaId: fpgaId,
name: resourceName, name: resourceName,
devices: make(map[string]deviceplugin.DeviceInfo), devices: make(map[string]deviceplugin.DeviceInfo),
root: rootDir, root: rootDir,
mode: mode,
} }
} }
// Discovers all FPGA devices available on the local node by walking `/sys/class/fpga` directory. // Discovers all FPGA devices available on the local node by walking `/sys/class/fpga` directory.
func discoverFPGAs(sysfsDir string, devfsDir string) (map[string]map[string]deviceplugin.DeviceInfo, error) { func discoverFPGAs(sysfsDir string, devfsDir string, mode pluginMode) (map[string]map[string]deviceplugin.DeviceInfo, error) {
deviceReg := regexp.MustCompile(deviceRE) deviceReg := regexp.MustCompile(deviceRE)
portReg := regexp.MustCompile(portRE) portReg := regexp.MustCompile(portRE)
fmeReg := regexp.MustCompile(fmeRE)
result := make(map[string]map[string]deviceplugin.DeviceInfo) result := make(map[string]map[string]deviceplugin.DeviceInfo)
@ -78,22 +101,53 @@ func discoverFPGAs(sysfsDir string, devfsDir string) (map[string]map[string]devi
for _, fpgaFile := range fpgaFiles { for _, fpgaFile := range fpgaFiles {
fname := fpgaFile.Name() fname := fpgaFile.Name()
if deviceReg.MatchString(fname) { if deviceReg.MatchString(fname) {
var interfaceId string
deviceFolder := path.Join(sysfsDir, fname) deviceFolder := path.Join(sysfsDir, fname)
deviceFiles, err := ioutil.ReadDir(deviceFolder) deviceFiles, err := ioutil.ReadDir(deviceFolder)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fpgaNodes := make(map[string][]string) fpgaNodes := make(map[string][]string)
if mode == regionMode {
for _, deviceFile := range deviceFiles {
name := deviceFile.Name()
if fmeReg.MatchString(name) {
if len(interfaceId) > 0 {
return nil, fmt.Errorf("Detected more than one FPGA region for device %s. Only one region per FPGA device is supported", fname)
}
interfaceIdFile := path.Join(deviceFolder, name, "pr", "interface_id")
data, err := ioutil.ReadFile(interfaceIdFile)
if err != nil {
return nil, err
}
interfaceId = strings.TrimSpace(string(data))
fpgaNodes[interfaceId] = append(fpgaNodes[interfaceId], name)
}
}
}
for _, deviceFile := range deviceFiles { for _, deviceFile := range deviceFiles {
name := deviceFile.Name() name := deviceFile.Name()
if portReg.MatchString(name) { if portReg.MatchString(name) {
afuFile := path.Join(deviceFolder, name, "afu_id") switch mode {
data, err := ioutil.ReadFile(afuFile) case regionMode:
if err != nil { if len(interfaceId) == 0 {
return nil, err return nil, fmt.Errorf("No FPGA region found for %s", fname)
}
fpgaNodes[interfaceId] = append(fpgaNodes[interfaceId], name)
case afMode:
afuFile := path.Join(deviceFolder, name, "afu_id")
data, err := ioutil.ReadFile(afuFile)
if err != nil {
return nil, err
}
afuID := strings.TrimSpace(string(data))
fpgaNodes[afuID] = append(fpgaNodes[afuID], name)
default:
glog.Fatal("Unsupported mode")
} }
afuID := strings.TrimSpace(string(data))
fpgaNodes[afuID] = append(fpgaNodes[afuID], name)
} }
} }
if len(fpgaNodes) == 0 { if len(fpgaNodes) == 0 {
@ -133,7 +187,7 @@ func (dm *deviceManager) ListAndWatch(empty *pluginapi.Empty, stream pluginapi.D
sysfsDir := path.Join(dm.root, sysfsDirectory) sysfsDir := path.Join(dm.root, sysfsDirectory)
devfsDir := path.Join(dm.root, devfsDirectory) devfsDir := path.Join(dm.root, devfsDirectory)
for { for {
devs, err := discoverFPGAs(sysfsDir, devfsDir) devs, err := discoverFPGAs(sysfsDir, devfsDir, dm.mode)
if err != nil { if err != nil {
dm.srv.Stop() dm.srv.Stop()
return fmt.Errorf("Device discovery failed: %+v", err) return fmt.Errorf("Device discovery failed: %+v", err)
@ -173,10 +227,19 @@ func (dm *deviceManager) PreStartContainer(ctx context.Context, rqt *pluginapi.P
} }
func main() { func main() {
flag.Parse() var modeStr string
fmt.Println("FPGA device plugin started")
devs, err := discoverFPGAs(sysfsDirectory, devfsDirectory) flag.StringVar(&modeStr, "mode", "af", "device plugin mode: 'af' (default) or 'region'")
flag.Parse()
mode := parseMode(modeStr)
if mode == wrongMode {
glog.Fatal("Wrong mode: ", modeStr)
}
fmt.Println("FPGA device plugin started in", modeStr, "mode")
devs, err := discoverFPGAs(sysfsDirectory, devfsDirectory, mode)
if err != nil { if err != nil {
glog.Fatalf("Device discovery failed: %+v", err) glog.Fatalf("Device discovery failed: %+v", err)
} }
@ -190,7 +253,7 @@ func main() {
for fpgaId, _ := range devs { for fpgaId, _ := range devs {
resourceName := resourceNamePrefix + "-" + fpgaId resourceName := resourceNamePrefix + "-" + fpgaId
pPrefix := pluginEndpointPrefix + "-" + fpgaId pPrefix := pluginEndpointPrefix + "-" + fpgaId
dm := newDeviceManager(resourceName, fpgaId, "/") dm := newDeviceManager(resourceName, fpgaId, "/", mode)
go func() { go func() {
ch <- dm.srv.Serve(dm, resourceName, pPrefix) ch <- dm.srv.Serve(dm, resourceName, pPrefix)

View File

@ -66,20 +66,24 @@ func TestDiscoverFPGAs(t *testing.T) {
sysfsfiles map[string][]byte sysfsfiles map[string][]byte
expectedResult map[string]map[string]deviceplugin.DeviceInfo expectedResult map[string]map[string]deviceplugin.DeviceInfo
expectedErr bool expectedErr bool
mode pluginMode
}{ }{
{ {
expectedResult: nil, expectedResult: nil,
expectedErr: true, expectedErr: true,
mode: afMode,
}, },
{ {
sysfsdirs: []string{"intel-fpga-dev.0"}, sysfsdirs: []string{"intel-fpga-dev.0"},
expectedResult: nil, expectedResult: nil,
expectedErr: true, expectedErr: true,
mode: afMode,
}, },
{ {
sysfsdirs: []string{"intel-fpga-dev.0/intel-fpga-port.0"}, sysfsdirs: []string{"intel-fpga-dev.0/intel-fpga-port.0"},
expectedResult: nil, expectedResult: nil,
expectedErr: true, expectedErr: true,
mode: afMode,
}, },
{ {
sysfsdirs: []string{ sysfsdirs: []string{
@ -90,6 +94,7 @@ func TestDiscoverFPGAs(t *testing.T) {
}, },
expectedResult: nil, expectedResult: nil,
expectedErr: true, expectedErr: true,
mode: afMode,
}, },
{ {
sysfsdirs: []string{ sysfsdirs: []string{
@ -135,6 +140,81 @@ func TestDiscoverFPGAs(t *testing.T) {
}, },
}, },
expectedErr: false, expectedErr: false,
mode: afMode,
},
{
sysfsdirs: []string{
"intel-fpga-dev.0/intel-fpga-fme.0/pr",
"intel-fpga-dev.0/intel-fpga-fme.1/pr",
},
sysfsfiles: map[string][]byte{
"intel-fpga-dev.0/intel-fpga-fme.0/pr/interface_id": []byte("d8424dc4a4a3c413f89e433683f9040b\n"),
"intel-fpga-dev.0/intel-fpga-fme.1/pr/interface_id": []byte("d8424dc4a4a3c413f89e433683f9040b\n"),
},
expectedResult: nil,
expectedErr: true,
mode: regionMode,
},
{
sysfsdirs: []string{
"intel-fpga-dev.0/intel-fpga-port.0",
"intel-fpga-dev.1/intel-fpga-port.1",
},
expectedResult: nil,
expectedErr: true,
mode: regionMode,
},
{
sysfsdirs: []string{
"intel-fpga-dev.0/intel-fpga-port.0",
"intel-fpga-dev.0/intel-fpga-fme.0/pr",
"intel-fpga-dev.1/intel-fpga-port.1",
"intel-fpga-dev.1/intel-fpga-fme.1/pr",
"intel-fpga-dev.2/intel-fpga-port.2",
"intel-fpga-dev.2/intel-fpga-fme.2/pr",
},
sysfsfiles: map[string][]byte{
"intel-fpga-dev.0/intel-fpga-port.0/afu_id": []byte("d8424dc4a4a3c413f89e433683f9040b\n"),
"intel-fpga-dev.1/intel-fpga-port.1/afu_id": []byte("d8424dc4a4a3c413f89e433683f9040b\n"),
"intel-fpga-dev.2/intel-fpga-port.2/afu_id": []byte("47595d0fae972fbed0c51b4a41c7a349\n"),
"intel-fpga-dev.0/intel-fpga-fme.0/pr/interface_id": []byte("ce48969398f05f33946d560708be108a\n"),
"intel-fpga-dev.1/intel-fpga-fme.1/pr/interface_id": []byte("ce48969398f05f33946d560708be108a\n"),
"intel-fpga-dev.2/intel-fpga-fme.2/pr/interface_id": []byte("fd967345645f05f338462a0748be0091\n"),
},
devfsdirs: []string{
"intel-fpga-port.0", "intel-fpga-fme.0",
"intel-fpga-port.1", "intel-fpga-fme.1",
"intel-fpga-port.2", "intel-fpga-fme.2",
},
expectedResult: map[string]map[string]deviceplugin.DeviceInfo{
"ce48969398f05f33946d560708be108a": map[string]deviceplugin.DeviceInfo{
"intel-fpga-dev.0": deviceplugin.DeviceInfo{
State: "Healthy",
Nodes: []string{
path.Join(tmpdir, "/dev/intel-fpga-fme.0"),
path.Join(tmpdir, "/dev/intel-fpga-port.0"),
},
},
"intel-fpga-dev.1": deviceplugin.DeviceInfo{
State: "Healthy",
Nodes: []string{
path.Join(tmpdir, "/dev/intel-fpga-fme.1"),
path.Join(tmpdir, "/dev/intel-fpga-port.1"),
},
},
},
"fd967345645f05f338462a0748be0091": map[string]deviceplugin.DeviceInfo{
"intel-fpga-dev.2": deviceplugin.DeviceInfo{
State: "Healthy",
Nodes: []string{
path.Join(tmpdir, "/dev/intel-fpga-fme.2"),
path.Join(tmpdir, "/dev/intel-fpga-port.2"),
},
},
},
},
expectedErr: false,
mode: regionMode,
}, },
} }
@ -144,7 +224,7 @@ func TestDiscoverFPGAs(t *testing.T) {
t.Error(err) t.Error(err)
} }
result, err := discoverFPGAs(sysfs, devfs) result, err := discoverFPGAs(sysfs, devfs, tcase.mode)
if tcase.expectedErr && err == nil { if tcase.expectedErr && err == nil {
t.Error("Expected error hasn't been triggered") t.Error("Expected error hasn't been triggered")
} }
@ -257,7 +337,7 @@ func TestListAndWatch(t *testing.T) {
} }
resourceName := resourceNamePrefix + "-" + afuID resourceName := resourceNamePrefix + "-" + afuID
testDM := newDeviceManager(resourceName, afuID, tmpdir) testDM := newDeviceManager(resourceName, afuID, tmpdir, afMode)
if testDM == nil { if testDM == nil {
t.Fatal("Failed to create a deviceManager") t.Fatal("Failed to create a deviceManager")
} }
@ -306,7 +386,7 @@ func TestListAndWatch(t *testing.T) {
} }
func TestAllocate(t *testing.T) { func TestAllocate(t *testing.T) {
testDM := newDeviceManager("", "", "") testDM := newDeviceManager("", "", "", afMode)
if testDM == nil { if testDM == nil {
t.Fatal("Failed to create a deviceManager") t.Fatal("Failed to create a deviceManager")
} }
@ -329,3 +409,29 @@ func TestAllocate(t *testing.T) {
t.Fatal("Allocated wrong number of devices") t.Fatal("Allocated wrong number of devices")
} }
} }
func TestParseMode(t *testing.T) {
tcases := []struct {
input string
output pluginMode
}{
{
input: "af",
output: afMode,
},
{
input: "region",
output: regionMode,
},
{
input: "unparsable",
output: wrongMode,
},
}
for _, tcase := range tcases {
if parseMode(tcase.input) != tcase.output {
t.Error("Wrong output", tcase.output, "for the given input", tcase.input)
}
}
}