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 support for get devlink resource #936

Merged
merged 2 commits into from
Jan 22, 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
192 changes: 192 additions & 0 deletions devlink_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,169 @@ type DevlinkDeviceInfo struct {
FwUndi string
}

// DevlinkResource represents a device resource
type DevlinkResource struct {
Name string
ID uint64
Size uint64
SizeNew uint64
SizeMin uint64
SizeMax uint64
SizeGranularity uint64
PendingChange bool
Unit uint8
SizeValid bool
OCCValid bool
OCCSize uint64
Parent *DevlinkResource
Children []DevlinkResource
}

// parseAttributes parses provided Netlink Attributes and populates DevlinkResource, returns error if occured
func (dlr *DevlinkResource) parseAttributes(attrs map[uint16]syscall.NetlinkRouteAttr) error {
var attr syscall.NetlinkRouteAttr
var ok bool

// mandatory attributes
attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_ID]
if !ok {
return fmt.Errorf("missing resource id")
}
dlr.ID = native.Uint64(attr.Value)

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_NAME]
if !ok {
return fmt.Errorf("missing resource name")
}
dlr.Name = nl.BytesToString(attr.Value)

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE]
if !ok {
return fmt.Errorf("missing resource size")
}
dlr.Size = native.Uint64(attr.Value)

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_GRAN]
if !ok {
return fmt.Errorf("missing resource size granularity")
}
dlr.SizeGranularity = native.Uint64(attr.Value)

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_UNIT]
if !ok {
return fmt.Errorf("missing resource unit")
}
dlr.Unit = uint8(attr.Value[0])

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_MIN]
if !ok {
return fmt.Errorf("missing resource size min")
}
dlr.SizeMin = native.Uint64(attr.Value)

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_MAX]
if !ok {
return fmt.Errorf("missing resource size max")
}
dlr.SizeMax = native.Uint64(attr.Value)

// optional attributes
attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_OCC]
if ok {
dlr.OCCSize = native.Uint64(attr.Value)
dlr.OCCValid = true
}

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_VALID]
if ok {
dlr.SizeValid = uint8(attr.Value[0]) != 0
}

dlr.SizeNew = dlr.Size
attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_NEW]
if ok {
dlr.SizeNew = native.Uint64(attr.Value)
}

dlr.PendingChange = dlr.Size != dlr.SizeNew

attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_LIST]
if ok {
// handle nested resoruces recursively
subResources, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return err
}

for _, subresource := range subResources {
resource := DevlinkResource{Parent: dlr}
attrs, err := nl.ParseRouteAttrAsMap(subresource.Value)
if err != nil {
return err
}
err = resource.parseAttributes(attrs)
if err != nil {
return fmt.Errorf("failed to parse child resource, parent:%s. %w", dlr.Name, err)
}
dlr.Children = append(dlr.Children, resource)
}
}
return nil
}

// DevlinkResources represents all devlink resources of a devlink device
type DevlinkResources struct {
Bus string
Device string
Resources []DevlinkResource
}

// parseAttributes parses provided Netlink Attributes and populates DevlinkResources, returns error if occured
func (dlrs *DevlinkResources) parseAttributes(attrs map[uint16]syscall.NetlinkRouteAttr) error {
var attr syscall.NetlinkRouteAttr
var ok bool

// Bus
attr, ok = attrs[nl.DEVLINK_ATTR_BUS_NAME]
if !ok {
return fmt.Errorf("missing bus name")
}
dlrs.Bus = nl.BytesToString(attr.Value)

// Device
attr, ok = attrs[nl.DEVLINK_ATTR_DEV_NAME]
if !ok {
return fmt.Errorf("missing device name")
}
dlrs.Device = nl.BytesToString(attr.Value)

// Resource List
attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_LIST]
if !ok {
return fmt.Errorf("missing resource list")
}

resourceAttrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return err
}

for _, resourceAttr := range resourceAttrs {
resource := DevlinkResource{}
attrs, err := nl.ParseRouteAttrAsMap(resourceAttr.Value)
if err != nil {
return err
}
err = resource.parseAttributes(attrs)
if err != nil {
return fmt.Errorf("failed to parse root resoruces, %w", err)
}
dlrs.Resources = append(dlrs.Resources, resource)
}

return nil
}

func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) {
devices := make([]*DevlinkDevice, 0, len(msgs))
for _, m := range msgs {
Expand Down Expand Up @@ -443,6 +606,35 @@ func (h *Handle) DevLinkGetPortByIndex(Bus string, Device string, PortIndex uint
return port, err
}

// DevlinkGetDeviceResources returns devlink device resources
func DevlinkGetDeviceResources(bus string, device string) (*DevlinkResources, error) {
return pkgHandle.DevlinkGetDeviceResources(bus, device)
}

// DevlinkGetDeviceResources returns devlink device resources
func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkResources, error) {
_, req, err := h.createCmdReq(nl.DEVLINK_CMD_RESOURCE_DUMP, bus, device)
if err != nil {
return nil, err
}

respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return nil, err
}

var resources DevlinkResources
for _, m := range respmsg {
attrs, err := nl.ParseRouteAttrAsMap(m[nl.SizeofGenlmsg:])
if err != nil {
return nil, err
}
resources.parseAttributes(attrs)
}

return &resources, nil
}

// 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
24 changes: 24 additions & 0 deletions devlink_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build linux
// +build linux

package netlink
Expand Down Expand Up @@ -262,3 +263,26 @@ func areInfoStructsEqual(first *DevlinkDeviceInfo, second *DevlinkDeviceInfo) bo
}
return true
}

func TestDevlinkGetDeviceResources(t *testing.T) {
minKernelRequired(t, 5, 11)
tearDown := setUpNetlinkTestWithKModule(t, "devlink")
defer tearDown()

if bus == "" || device == "" {
//TODO: setup netdevsim device instead of getting device from flags
t.Log("devlink bus and device are empty, skipping test")
t.SkipNow()
}

res, err := DevlinkGetDeviceResources(bus, device)
if err != nil {
t.Fatalf("failed to get device(%s/%s) resources. %s", bus, device, err)
}

if res.Bus != bus || res.Device != device {
t.Fatalf("missmatching bus/device")
}

t.Logf("Resources: %+v", res)
}
79 changes: 49 additions & 30 deletions nl/devlink_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,54 @@ const (
)

const (
DEVLINK_CMD_GET = 1
DEVLINK_CMD_PORT_GET = 5
DEVLINK_CMD_PORT_SET = 6
DEVLINK_CMD_PORT_NEW = 7
DEVLINK_CMD_PORT_DEL = 8
DEVLINK_CMD_ESWITCH_GET = 29
DEVLINK_CMD_ESWITCH_SET = 30
DEVLINK_CMD_INFO_GET = 51
DEVLINK_CMD_GET = 1
DEVLINK_CMD_PORT_GET = 5
DEVLINK_CMD_PORT_SET = 6
DEVLINK_CMD_PORT_NEW = 7
DEVLINK_CMD_PORT_DEL = 8
DEVLINK_CMD_ESWITCH_GET = 29
DEVLINK_CMD_ESWITCH_SET = 30
DEVLINK_CMD_RESOURCE_DUMP = 36
DEVLINK_CMD_INFO_GET = 51
)

const (
DEVLINK_ATTR_BUS_NAME = 1
DEVLINK_ATTR_DEV_NAME = 2
DEVLINK_ATTR_PORT_INDEX = 3
DEVLINK_ATTR_PORT_TYPE = 4
DEVLINK_ATTR_PORT_NETDEV_IFINDEX = 6
DEVLINK_ATTR_PORT_NETDEV_NAME = 7
DEVLINK_ATTR_PORT_IBDEV_NAME = 8
DEVLINK_ATTR_ESWITCH_MODE = 25
DEVLINK_ATTR_ESWITCH_INLINE_MODE = 26
DEVLINK_ATTR_ESWITCH_ENCAP_MODE = 62
DEVLINK_ATTR_PORT_FLAVOUR = 77
DEVLINK_ATTR_INFO_DRIVER_NAME = 98
DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99
DEVLINK_ATTR_INFO_VERSION_FIXED = 100
DEVLINK_ATTR_INFO_VERSION_RUNNING = 101
DEVLINK_ATTR_INFO_VERSION_STORED = 102
DEVLINK_ATTR_INFO_VERSION_NAME = 103
DEVLINK_ATTR_INFO_VERSION_VALUE = 104
DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127
DEVLINK_ATTR_PORT_FUNCTION = 145
DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150
DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 164
DEVLINK_ATTR_BUS_NAME = 1
DEVLINK_ATTR_DEV_NAME = 2
DEVLINK_ATTR_PORT_INDEX = 3
DEVLINK_ATTR_PORT_TYPE = 4
DEVLINK_ATTR_PORT_NETDEV_IFINDEX = 6
DEVLINK_ATTR_PORT_NETDEV_NAME = 7
DEVLINK_ATTR_PORT_IBDEV_NAME = 8
DEVLINK_ATTR_ESWITCH_MODE = 25
DEVLINK_ATTR_ESWITCH_INLINE_MODE = 26
DEVLINK_ATTR_ESWITCH_ENCAP_MODE = 62
DEVLINK_ATTR_RESOURCE_LIST = 63 /* nested */
DEVLINK_ATTR_RESOURCE = 64 /* nested */
DEVLINK_ATTR_RESOURCE_NAME = 65 /* string */
DEVLINK_ATTR_RESOURCE_ID = 66 /* u64 */
DEVLINK_ATTR_RESOURCE_SIZE = 67 /* u64 */
DEVLINK_ATTR_RESOURCE_SIZE_NEW = 68 /* u64 */
DEVLINK_ATTR_RESOURCE_SIZE_VALID = 69 /* u8 */
DEVLINK_ATTR_RESOURCE_SIZE_MIN = 70 /* u64 */
DEVLINK_ATTR_RESOURCE_SIZE_MAX = 71 /* u64 */
DEVLINK_ATTR_RESOURCE_SIZE_GRAN = 72 /* u64 */
DEVLINK_ATTR_RESOURCE_UNIT = 73 /* u8 */
DEVLINK_ATTR_RESOURCE_OCC = 74 /* u64 */
DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID = 75 /* u64 */
DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS = 76 /* u64 */
DEVLINK_ATTR_PORT_FLAVOUR = 77
DEVLINK_ATTR_INFO_DRIVER_NAME = 98
DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99
DEVLINK_ATTR_INFO_VERSION_FIXED = 100
DEVLINK_ATTR_INFO_VERSION_RUNNING = 101
DEVLINK_ATTR_INFO_VERSION_STORED = 102
DEVLINK_ATTR_INFO_VERSION_NAME = 103
DEVLINK_ATTR_INFO_VERSION_VALUE = 104
DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127
DEVLINK_ATTR_PORT_FUNCTION = 145
DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150
DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 164
)

const (
Expand Down Expand Up @@ -94,3 +109,7 @@ const (
DEVLINK_PORT_FN_OPSTATE_DETACHED = 0
DEVLINK_PORT_FN_OPSTATE_ATTACHED = 1
)

const (
DEVLINK_RESOURCE_UNIT_ENTRY uint8 = 0
)
16 changes: 16 additions & 0 deletions nl/nl_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,22 @@ func ParseRouteAttr(b []byte) ([]syscall.NetlinkRouteAttr, error) {
return attrs, nil
}

// ParseRouteAttrAsMap parses provided buffer that contains raw RtAttrs and returns a map of parsed
// atttributes indexed by attribute type or error if occured.
func ParseRouteAttrAsMap(b []byte) (map[uint16]syscall.NetlinkRouteAttr, error) {
attrMap := make(map[uint16]syscall.NetlinkRouteAttr)

attrs, err := ParseRouteAttr(b)
if err != nil {
return nil, err
}

for _, attr := range attrs {
attrMap[attr.Attr.Type] = attr
}
return attrMap, nil
}

func netlinkRouteAttrAndValue(b []byte) (*unix.RtAttr, []byte, int, error) {
a := (*unix.RtAttr)(unsafe.Pointer(&b[0]))
if int(a.Len) < unix.SizeofRtAttr || int(a.Len) > len(b) {
Expand Down
22 changes: 22 additions & 0 deletions nl/nl_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,25 @@ func TestCnMsgOpDeserializeSerialize(t *testing.T) {
msg := DeserializeCnMsgOp(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}

func TestParseRouteAttrAsMap(t *testing.T) {
attr1 := NewRtAttr(0x1, ZeroTerminated("foo"))
attr2 := NewRtAttr(0x2, ZeroTerminated("bar"))
raw := make([]byte, 0)
raw = append(raw, attr1.Serialize()...)
raw = append(raw, attr2.Serialize()...)
attrs, err := ParseRouteAttrAsMap(raw)
if err != nil {
t.Errorf("failed to parse route attributes %s", err)
}

attr, ok := attrs[0x1]
if !ok || BytesToString(attr.Value) != "foo" {
t.Error("missing/incorrect \"foo\" attribute")
}

attr, ok = attrs[0x2]
if !ok || BytesToString(attr.Value) != "bar" {
t.Error("missing/incorrect \"bar\" attribute")
}
}
Loading