Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions to work with devlink device parameters #950

Merged
merged 1 commit into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions devlink_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,107 @@ func (dlrs *DevlinkResources) parseAttributes(attrs map[uint16]syscall.NetlinkRo
return nil
}

// DevlinkParam represents parameter of the device
type DevlinkParam struct {
ykulazhenkov marked this conversation as resolved.
Show resolved Hide resolved
Name string
IsGeneric bool
Type uint8 // possible values are in nl.DEVLINK_PARAM_TYPE_* constants
Values []DevlinkParamValue
}

// DevlinkParamValue contains values of the parameter
// Data field contains specific type which can be casted by unsing info from the DevlinkParam.Type field
type DevlinkParamValue struct {
rawData []byte
Data interface{}
CMODE uint8 // possible values are in nl.DEVLINK_PARAM_CMODE_* constants
}

// parseAttributes parses provided Netlink Attributes and populates DevlinkParam, returns error if occured
func (dlp *DevlinkParam) parseAttributes(attrs []syscall.NetlinkRouteAttr) error {
var valuesList [][]syscall.NetlinkRouteAttr
for _, attr := range attrs {
switch attr.Attr.Type {
case nl.DEVLINK_ATTR_PARAM:
nattrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return err
}
for _, nattr := range nattrs {
switch nattr.Attr.Type {
case nl.DEVLINK_ATTR_PARAM_NAME:
dlp.Name = nl.BytesToString(nattr.Value)
case nl.DEVLINK_ATTR_PARAM_GENERIC:
dlp.IsGeneric = true
case nl.DEVLINK_ATTR_PARAM_TYPE:
if len(nattr.Value) == 1 {
dlp.Type = nattr.Value[0]
}
case nl.DEVLINK_ATTR_PARAM_VALUES_LIST:
nnattrs, err := nl.ParseRouteAttr(nattr.Value)
if err != nil {
return err
}
valuesList = append(valuesList, nnattrs)
}
}
}
}
for _, valAttr := range valuesList {
v := DevlinkParamValue{}
if err := v.parseAttributes(valAttr, dlp.Type); err != nil {
return err
}
dlp.Values = append(dlp.Values, v)
}
return nil
}

func (dlpv *DevlinkParamValue) parseAttributes(attrs []syscall.NetlinkRouteAttr, paramType uint8) error {
ykulazhenkov marked this conversation as resolved.
Show resolved Hide resolved
for _, attr := range attrs {
nattrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return err
}
var rawData []byte
for _, nattr := range nattrs {
switch nattr.Attr.Type {
case nl.DEVLINK_ATTR_PARAM_VALUE_DATA:
rawData = nattr.Value
case nl.DEVLINK_ATTR_PARAM_VALUE_CMODE:
if len(nattr.Value) == 1 {
dlpv.CMODE = nattr.Value[0]
}
}
}
switch paramType {
case nl.DEVLINK_PARAM_TYPE_U8:
dlpv.Data = uint8(0)
if rawData != nil && len(rawData) == 1 {
dlpv.Data = uint8(rawData[0])
}
case nl.DEVLINK_PARAM_TYPE_U16:
dlpv.Data = uint16(0)
if rawData != nil {
dlpv.Data = native.Uint16(rawData)
}
case nl.DEVLINK_PARAM_TYPE_U32:
dlpv.Data = uint32(0)
if rawData != nil {
dlpv.Data = native.Uint32(rawData)
}
case nl.DEVLINK_PARAM_TYPE_STRING:
dlpv.Data = ""
if rawData != nil {
dlpv.Data = nl.BytesToString(rawData)
}
case nl.DEVLINK_PARAM_TYPE_BOOL:
dlpv.Data = rawData != nil
}
}
return nil
}

func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) {
devices := make([]*DevlinkDevice, 0, len(msgs))
for _, m := range msgs {
Expand Down Expand Up @@ -635,6 +736,144 @@ func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkR
return &resources, nil
}

// DevlinkGetDeviceParams returns parameters for devlink device
// Equivalent to: `devlink dev param show <bus>/<device>`
func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) {
_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device)
if err != nil {
return nil, err
}
req.Flags |= unix.NLM_F_DUMP
respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return nil, err
}
var params []*DevlinkParam
for _, m := range respmsg {
attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:])
if err != nil {
return nil, err
}
p := &DevlinkParam{}
if err := p.parseAttributes(attrs); err != nil {
return nil, err
}
params = append(params, p)
}

return params, nil
}

// DevlinkGetDeviceParams returns parameters for devlink device
// Equivalent to: `devlink dev param show <bus>/<device>`
func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) {
return pkgHandle.DevlinkGetDeviceParams(bus, device)
}

// DevlinkGetDeviceParamByName returns specific parameter for devlink device
// Equivalent to: `devlink dev param show <bus>/<device> name <param>`
func (h *Handle) DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) {
_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device)
if err != nil {
return nil, err
}
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param)))
respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return nil, err
}
if len(respmsg) == 0 {
return nil, fmt.Errorf("unexpected response")
}
attrs, err := nl.ParseRouteAttr(respmsg[0][nl.SizeofGenlmsg:])
if err != nil {
return nil, err
}
p := &DevlinkParam{}
if err := p.parseAttributes(attrs); err != nil {
return nil, err
}
return p, nil
}

// DevlinkGetDeviceParamByName returns specific parameter for devlink device
// Equivalent to: `devlink dev param show <bus>/<device> name <param>`
func DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) {
return pkgHandle.DevlinkGetDeviceParamByName(bus, device, param)
}

// DevlinkSetDeviceParam set specific parameter for devlink device
// Equivalent to: `devlink dev param set <bus>/<device> name <param> cmode <cmode> value <value>`
// cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants
// value argument should have one of the following types: uint8, uint16, uint32, string, bool
func (h *Handle) DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error {
// retrive the param type
p, err := h.DevlinkGetDeviceParamByName(bus, device, param)
if err != nil {
return fmt.Errorf("failed to get device param: %v", err)
}
paramType := p.Type

_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_SET, bus, device)
if err != nil {
return err
}
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_TYPE, nl.Uint8Attr(paramType)))
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param)))
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_CMODE, nl.Uint8Attr(cmode)))

var valueAsBytes []byte
switch paramType {
case nl.DEVLINK_PARAM_TYPE_U8:
v, ok := value.(uint8)
if !ok {
return fmt.Errorf("unepected value type required: uint8, actual: %T", value)
}
valueAsBytes = nl.Uint8Attr(v)
case nl.DEVLINK_PARAM_TYPE_U16:
v, ok := value.(uint16)
if !ok {
return fmt.Errorf("unepected value type required: uint16, actual: %T", value)
}
valueAsBytes = nl.Uint16Attr(v)
case nl.DEVLINK_PARAM_TYPE_U32:
v, ok := value.(uint32)
if !ok {
return fmt.Errorf("unepected value type required: uint32, actual: %T", value)
}
valueAsBytes = nl.Uint32Attr(v)
case nl.DEVLINK_PARAM_TYPE_STRING:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unepected value type required: string, actual: %T", value)
}
valueAsBytes = nl.ZeroTerminated(v)
case nl.DEVLINK_PARAM_TYPE_BOOL:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unepected value type required: bool, actual: %T", value)
}
if v {
valueAsBytes = []byte{}
}
default:
return fmt.Errorf("unsupported parameter type: %d", paramType)
}
if valueAsBytes != nil {
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_DATA, valueAsBytes))
}
_, err = req.Execute(unix.NETLINK_GENERIC, 0)
return err
}

// DevlinkSetDeviceParam set specific parameter for devlink device
// Equivalent to: `devlink dev param set <bus>/<device> name <param> cmode <cmode> value <value>`
// cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants
// value argument should have one of the following types: uint8, uint16, uint32, string, bool
func DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error {
return pkgHandle.DevlinkSetDeviceParam(bus, device, param, cmode, value)
}

// DevLinkGetPortByIndex provides a pointer to devlink portand nil error,
// otherwise returns an error code.
func DevLinkGetPortByIndex(Bus string, Device string, PortIndex uint32) (*DevlinkPort, error) {
Expand Down
116 changes: 116 additions & 0 deletions devlink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ package netlink

import (
"flag"
"math/rand"
"net"
"os"
"strconv"
"testing"

"github.com/vishvananda/netlink/nl"
)

func TestDevLinkGetDeviceList(t *testing.T) {
Expand Down Expand Up @@ -286,3 +291,114 @@ func TestDevlinkGetDeviceResources(t *testing.T) {

t.Logf("Resources: %+v", res)
}

// devlink device parameters can be tested with netdevsim
// function will create netdevsim/netdevsim<random_id> virtual device that can be used for testing
// netdevsim module should be loaded to run devlink param tests
func setupDevlinkDeviceParamTest(t *testing.T) (string, string, func()) {
t.Helper()
skipUnlessRoot(t)
skipUnlessKModuleLoaded(t, "netdevsim")
testDevID := strconv.Itoa(1000 + rand.Intn(1000))
err := os.WriteFile("/sys/bus/netdevsim/new_device", []byte(testDevID), 0755)
if err != nil {
t.Fatalf("can't create netdevsim test device %s: %v", testDevID, err)
}

return "netdevsim", "netdevsim" + testDevID, func() {
_ = os.WriteFile("/sys/bus/netdevsim/del_device", []byte(testDevID), 0755)
}
}

func TestDevlinkGetDeviceParams(t *testing.T) {
busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t)
defer cleanupFunc()
params, err := DevlinkGetDeviceParams(busName, deviceName)
if err != nil {
t.Fatalf("failed to get device(%s/%s) parameters. %s", busName, deviceName, err)
}
if len(params) == 0 {
t.Fatal("parameters list is empty")
}
for _, p := range params {
validateDeviceParams(t, p)
}
}

func TestDevlinkGetDeviceParamByName(t *testing.T) {
busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t)
defer cleanupFunc()
param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs")
if err != nil {
t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err)
}
validateDeviceParams(t, param)
}

func TestDevlinkSetDeviceParam(t *testing.T) {
busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t)
defer cleanupFunc()
err := DevlinkSetDeviceParam(busName, deviceName, "max_macs", nl.DEVLINK_PARAM_CMODE_DRIVERINIT, uint32(8))
if err != nil {
t.Fatalf("failed to set max_macs for device(%s/%s): %s", busName, deviceName, err)
}
param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs")
if err != nil {
t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err)
}
validateDeviceParams(t, param)
v, ok := param.Values[0].Data.(uint32)
if !ok {
t.Fatalf("unexpected value")
}
if v != uint32(8) {
t.Fatalf("value not set")
}
}

func validateDeviceParams(t *testing.T, p *DevlinkParam) {
if p.Name == "" {
t.Fatal("Name field not set")
}
if p.Name == "max_macs" && !p.IsGeneric {
t.Fatal("IsGeneric should be true for generic parameter")
}
// test1 is a driver-specific parameter in netdevsim device, check should
// also path on HW devices
if p.Name == "test1" && p.IsGeneric {
t.Fatal("IsGeneric should be false for driver-specific parameter")
}
switch p.Type {
case nl.DEVLINK_PARAM_TYPE_U8,
nl.DEVLINK_PARAM_TYPE_U16,
nl.DEVLINK_PARAM_TYPE_U32,
nl.DEVLINK_PARAM_TYPE_STRING,
nl.DEVLINK_PARAM_TYPE_BOOL:
default:
t.Fatal("Type has unexpected value")
}
if len(p.Values) == 0 {
t.Fatal("Values are not set")
}
for _, v := range p.Values {
switch v.CMODE {
case nl.DEVLINK_PARAM_CMODE_RUNTIME,
nl.DEVLINK_PARAM_CMODE_DRIVERINIT,
nl.DEVLINK_PARAM_CMODE_PERMANENT:
default:
t.Fatal("CMODE has unexpected value")
}
if p.Name == "max_macs" {
_, ok := v.Data.(uint32)
if !ok {
t.Fatalf("value max_macs has wrong type: %T, expected: uint32", v.Data)
}
}
if p.Name == "test1" {
_, ok := v.Data.(bool)
if !ok {
t.Fatalf("value test1 has wrong type: %T, expected: bool", v.Data)
}
}
}
}
Loading
Loading