From 02fc0deed20decabf969957e6ee8528661f72272 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sat, 9 Nov 2019 01:27:36 +0300 Subject: [PATCH 1/9] devlink: pass netlink data in client.execute() function Extend this function arguments list in order to support passing netlink data (data []byte). Signed-off-by: Alexander Petrovskiy --- client_linux.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client_linux.go b/client_linux.go index eee4aa3..b4ce86c 100644 --- a/client_linux.go +++ b/client_linux.go @@ -45,7 +45,7 @@ func (c *client) Close() error { return c.c.Close() } // Devices implements osClient. func (c *client) Devices() ([]*Device, error) { - msgs, err := c.execute(unix.DEVLINK_CMD_GET, netlink.Dump) + msgs, err := c.execute(unix.DEVLINK_CMD_GET, netlink.Dump, nil) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func (c *client) Devices() ([]*Device, error) { // Ports implements osClient. func (c *client) Ports() ([]*Port, error) { - msgs, err := c.execute(unix.DEVLINK_CMD_PORT_GET, netlink.Dump) + msgs, err := c.execute(unix.DEVLINK_CMD_PORT_GET, netlink.Dump, nil) if err != nil { return nil, err } @@ -65,13 +65,14 @@ func (c *client) Ports() ([]*Port, error) { // execute executes the specified command with additional header flags. The // netlink.Request header flag is automatically set. -func (c *client) execute(cmd uint8, flags netlink.HeaderFlags) ([]genetlink.Message, error) { +func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, data []byte) ([]genetlink.Message, error) { return c.c.Execute( genetlink.Message{ Header: genetlink.Header{ Command: cmd, Version: unix.DEVLINK_GENL_VERSION, }, + Data: data, }, // Always pass the genetlink family ID and request flag. c.family.ID, From 9a060828746713851c07da4ff596c02d425289c6 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sat, 9 Nov 2019 02:23:00 +0300 Subject: [PATCH 2/9] devlink: add devlink dpipe tables support Add DpipeTables() function to retrieve a list of all supported DPIPE tables for a specified device. The following information is collected per DPIPE table: - devlink bus - devlink device - table name - table size - table counters_enabled flag More info about DPIPE: https://github.com/Mellanox/mlxsw/wiki/Pipeline-Debugging-(DPIPE) Signed-off-by: Alexander Petrovskiy --- client.go | 15 ++++++ client_linux.go | 80 ++++++++++++++++++++++++++++++++ client_linux_integration_test.go | 24 ++++++++++ client_linux_test.go | 18 +++++++ client_others.go | 5 ++ 5 files changed, 142 insertions(+) diff --git a/client.go b/client.go index c2a570c..e6ff99f 100644 --- a/client.go +++ b/client.go @@ -39,11 +39,17 @@ func (c *Client) Ports() ([]*Port, error) { return c.c.Ports() } +// DpipeTables retrieves all devlink DPIPE tables attached to specified device on this system. +func (c *Client) DpipeTables(dev *Device) ([]*DpipeTable, error) { + return c.c.DpipeTables(dev) +} + // An osClient is the operating system-specific implementation of Client. type osClient interface { io.Closer Devices() ([]*Device, error) Ports() ([]*Port, error) + DpipeTables(*Device) ([]*DpipeTable, error) } // A Device is a devlink device. @@ -73,3 +79,12 @@ type Port struct { Type PortType Name string } + +// A DpipeTable is a devlink DPIPE table +type DpipeTable struct { + Bus string + Device string + Name string + Size uint64 + CountersEnabled bool +} diff --git a/client_linux.go b/client_linux.go index b4ce86c..e6a6fcf 100644 --- a/client_linux.go +++ b/client_linux.go @@ -3,6 +3,8 @@ package devlink import ( + "fmt" + "github.com/mdlayher/genetlink" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" @@ -63,6 +65,27 @@ func (c *client) Ports() ([]*Port, error) { return parsePorts(msgs) } +// DpipeTables implements osClient. +func (c *client) DpipeTables(dev *Device) ([]*DpipeTable, error) { + if dev == nil { + return nil, fmt.Errorf("Invalid argument") + } + encoder := netlink.NewAttributeEncoder() + encoder.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus) + encoder.String(unix.DEVLINK_ATTR_DEV_NAME, dev.Device) + data, err := encoder.Encode() + if err != nil { + return nil, err + } + + msgs, err := c.execute(unix.DEVLINK_CMD_DPIPE_TABLE_GET, netlink.Acknowledge, data) + if err != nil { + return nil, err + } + + return parseDpipeTables(msgs) +} + // execute executes the specified command with additional header flags. The // netlink.Request header flag is automatically set. func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, data []byte) ([]genetlink.Message, error) { @@ -140,3 +163,60 @@ func parsePorts(msgs []genetlink.Message) ([]*Port, error) { return ps, nil } + +// parseDpipeTables parses DPIPE tables from a slice of generic netlink messages. +func parseDpipeTables(msgs []genetlink.Message) ([]*DpipeTable, error) { + var bus, dev string + if len(msgs) == 0 { + // No devlink response found. + return nil, nil + } + + ts := make([]*DpipeTable, 0, len(msgs)) + for _, m := range msgs { + ad, err := netlink.NewAttributeDecoder(m.Data) + if err != nil { + return nil, err + } + + for ad.Next() { + switch ad.Type() { + case unix.DEVLINK_ATTR_BUS_NAME: + bus = ad.String() + case unix.DEVLINK_ATTR_DEV_NAME: + dev = ad.String() + case unix.DEVLINK_ATTR_DPIPE_TABLES: + tablesData := ad.Bytes() + adTables, err := netlink.NewAttributeDecoder(tablesData) + if err != nil { + continue + } + for adTables.Next() { + if adTables.Type() == unix.DEVLINK_ATTR_DPIPE_TABLE { + tableData := adTables.Bytes() + adTable, err := netlink.NewAttributeDecoder(tableData) + if err != nil { + continue + } + var t DpipeTable + t.Bus = bus + t.Device = dev + for adTable.Next() { + switch adTable.Type() { + case unix.DEVLINK_ATTR_DPIPE_TABLE_NAME: + t.Name = adTable.String() + case unix.DEVLINK_ATTR_DPIPE_TABLE_SIZE: + t.Size = adTable.Uint64() + case unix.DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED: + t.CountersEnabled = adTable.Uint8() != 0 + } + } + ts = append(ts, &t) + } + } + } + + } + } + return ts, nil +} diff --git a/client_linux_integration_test.go b/client_linux_integration_test.go index 5a5bb31..117ae55 100644 --- a/client_linux_integration_test.go +++ b/client_linux_integration_test.go @@ -43,4 +43,28 @@ func TestLinuxClientIntegration(t *testing.T) { t.Logf("port: %+v", p) } }) + + t.Run("dpipe_tables", func(t *testing.T) { + var tables []*devlink.DpipeTable + devices, err := c.Devices() + if err != nil { + t.Fatalf("failed to get devices: %v", err) + } + + for _, d := range devices { + tt, err := c.DpipeTables(d) + if err != nil { + t.Errorf("failed to get DPIPE table from device %v: %v", d, err) + } + tables = append(tables, tt...) + } + + if len(tables) == 0 { + t.Fatalf("failed to retrieve any DPIPE tables from devices") + } + + for _, table := range tables { + t.Logf("dpipe_table: %+v", table) + } + }) } diff --git a/client_linux_test.go b/client_linux_test.go index 8f6e8a8..9976dde 100644 --- a/client_linux_test.go +++ b/client_linux_test.go @@ -15,6 +15,10 @@ import ( ) func TestLinuxClientEmptyResponse(t *testing.T) { + const ( + bus = "pci" + device = "0000:01:00.0" + ) tests := []struct { name string fn func(t *testing.T, c *client) @@ -46,6 +50,20 @@ func TestLinuxClientEmptyResponse(t *testing.T) { } }, }, + { + name: "dpipe_tables", + fn: func(t *testing.T, c *client) { + dev := Device{bus, device} + tables, err := c.DpipeTables(&dev) + if err != nil { + t.Fatalf("failed to get DPIPE tables: %v", err) + } + + if diff := cmp.Diff(0, len(tables)); diff != "" { + t.Fatalf("unexpected number of DPIPE tables (-want +got):\n%s", diff) + } + }, + }, } for _, tt := range tests { diff --git a/client_others.go b/client_others.go index 3750961..3fe4d9f 100644 --- a/client_others.go +++ b/client_others.go @@ -38,3 +38,8 @@ func (c *client) Devices() ([]*Device, error) { func (c *client) Ports() ([]*Port, error) { return nil, errUnimplemented } + +// PID implements osClient. +func (c *client) DpipeTables(dev *Device) ([]*DpipeTable, error) { + return nil, errUnimplemented +} From 71b833eefa46fd35ed087b6e9d5e5c408f85af3b Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sat, 9 Nov 2019 02:34:22 +0300 Subject: [PATCH 3/9] devlink: fix error formatting Signed-off-by: Alexander Petrovskiy --- client_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client_linux.go b/client_linux.go index e6a6fcf..8f49b01 100644 --- a/client_linux.go +++ b/client_linux.go @@ -68,7 +68,7 @@ func (c *client) Ports() ([]*Port, error) { // DpipeTables implements osClient. func (c *client) DpipeTables(dev *Device) ([]*DpipeTable, error) { if dev == nil { - return nil, fmt.Errorf("Invalid argument") + return nil, fmt.Errorf("invalid argument") } encoder := netlink.NewAttributeEncoder() encoder.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus) From 800f11cfa1e24ca850ecb055b1b173b272ee17c4 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sun, 10 Nov 2019 20:26:27 +0300 Subject: [PATCH 4/9] devlink: fix DPIPE namings according to Go style Signed-off-by: Alexander Petrovskiy --- client.go | 14 ++++++++------ client_linux.go | 14 +++++++------- client_linux_integration_test.go | 4 ++-- client_linux_test.go | 2 +- client_others.go | 2 +- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index e6ff99f..1da67f8 100644 --- a/client.go +++ b/client.go @@ -39,9 +39,9 @@ func (c *Client) Ports() ([]*Port, error) { return c.c.Ports() } -// DpipeTables retrieves all devlink DPIPE tables attached to specified device on this system. -func (c *Client) DpipeTables(dev *Device) ([]*DpipeTable, error) { - return c.c.DpipeTables(dev) +// DPIPETables retrieves all devlink DPIPE tables attached to specified device on this system. +func (c *Client) DPIPETables(dev *Device) ([]*DPIPETable, error) { + return c.c.DPIPETables(dev) } // An osClient is the operating system-specific implementation of Client. @@ -49,7 +49,7 @@ type osClient interface { io.Closer Devices() ([]*Device, error) Ports() ([]*Port, error) - DpipeTables(*Device) ([]*DpipeTable, error) + DPIPETables(*Device) ([]*DPIPETable, error) } // A Device is a devlink device. @@ -80,8 +80,10 @@ type Port struct { Name string } -// A DpipeTable is a devlink DPIPE table -type DpipeTable struct { +// A DPIPETable is a devlink pipeline debugging (DPIPE) table. +// For more information on DPIPE, see: +// https://github.com/Mellanox/mlxsw/wiki/Pipeline-Debugging-(DPIPE) +type DPIPETable struct { Bus string Device string Name string diff --git a/client_linux.go b/client_linux.go index 8f49b01..ac0e4b7 100644 --- a/client_linux.go +++ b/client_linux.go @@ -65,8 +65,8 @@ func (c *client) Ports() ([]*Port, error) { return parsePorts(msgs) } -// DpipeTables implements osClient. -func (c *client) DpipeTables(dev *Device) ([]*DpipeTable, error) { +// DPIPETables implements osClient. +func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) { if dev == nil { return nil, fmt.Errorf("invalid argument") } @@ -83,7 +83,7 @@ func (c *client) DpipeTables(dev *Device) ([]*DpipeTable, error) { return nil, err } - return parseDpipeTables(msgs) + return parseDPIPETables(msgs) } // execute executes the specified command with additional header flags. The @@ -164,15 +164,15 @@ func parsePorts(msgs []genetlink.Message) ([]*Port, error) { return ps, nil } -// parseDpipeTables parses DPIPE tables from a slice of generic netlink messages. -func parseDpipeTables(msgs []genetlink.Message) ([]*DpipeTable, error) { +// parseDPIPETables parses DPIPE tables from a slice of generic netlink messages. +func parseDPIPETables(msgs []genetlink.Message) ([]*DPIPETable, error) { var bus, dev string if len(msgs) == 0 { // No devlink response found. return nil, nil } - ts := make([]*DpipeTable, 0, len(msgs)) + ts := make([]*DPIPETable, 0, len(msgs)) for _, m := range msgs { ad, err := netlink.NewAttributeDecoder(m.Data) if err != nil { @@ -198,7 +198,7 @@ func parseDpipeTables(msgs []genetlink.Message) ([]*DpipeTable, error) { if err != nil { continue } - var t DpipeTable + var t DPIPETable t.Bus = bus t.Device = dev for adTable.Next() { diff --git a/client_linux_integration_test.go b/client_linux_integration_test.go index 117ae55..51b35f7 100644 --- a/client_linux_integration_test.go +++ b/client_linux_integration_test.go @@ -45,14 +45,14 @@ func TestLinuxClientIntegration(t *testing.T) { }) t.Run("dpipe_tables", func(t *testing.T) { - var tables []*devlink.DpipeTable + var tables []*devlink.DPIPETable devices, err := c.Devices() if err != nil { t.Fatalf("failed to get devices: %v", err) } for _, d := range devices { - tt, err := c.DpipeTables(d) + tt, err := c.DPIPETables(d) if err != nil { t.Errorf("failed to get DPIPE table from device %v: %v", d, err) } diff --git a/client_linux_test.go b/client_linux_test.go index 9976dde..f42f000 100644 --- a/client_linux_test.go +++ b/client_linux_test.go @@ -54,7 +54,7 @@ func TestLinuxClientEmptyResponse(t *testing.T) { name: "dpipe_tables", fn: func(t *testing.T, c *client) { dev := Device{bus, device} - tables, err := c.DpipeTables(&dev) + tables, err := c.DPIPETables(&dev) if err != nil { t.Fatalf("failed to get DPIPE tables: %v", err) } diff --git a/client_others.go b/client_others.go index 3fe4d9f..9d2d238 100644 --- a/client_others.go +++ b/client_others.go @@ -40,6 +40,6 @@ func (c *client) Ports() ([]*Port, error) { } // PID implements osClient. -func (c *client) DpipeTables(dev *Device) ([]*DpipeTable, error) { +func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) { return nil, errUnimplemented } From 4668f0a686d2c035504f0858f965d4f3f9627574 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sun, 10 Nov 2019 20:31:00 +0300 Subject: [PATCH 5/9] devlink: fix AttributeEncoder variable naming Signed-off-by: Alexander Petrovskiy --- client_linux.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client_linux.go b/client_linux.go index ac0e4b7..509dfa0 100644 --- a/client_linux.go +++ b/client_linux.go @@ -70,10 +70,10 @@ func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) { if dev == nil { return nil, fmt.Errorf("invalid argument") } - encoder := netlink.NewAttributeEncoder() - encoder.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus) - encoder.String(unix.DEVLINK_ATTR_DEV_NAME, dev.Device) - data, err := encoder.Encode() + ae := netlink.NewAttributeEncoder() + ae.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus) + ae.String(unix.DEVLINK_ATTR_DEV_NAME, dev.Device) + data, err := ae.Encode() if err != nil { return nil, err } From 3b4eda9db8b3766b4e88e6d97bc23d163b76d4b6 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sun, 10 Nov 2019 20:47:34 +0300 Subject: [PATCH 6/9] devlink: change execute() function arguments Change it to accept 'ae *netlink.AttributeEncoder' as an optional argument to pass 'ae' with data to encode for netlink message instead of data itself. Signed-off-by: Alexander Petrovskiy --- client_linux.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/client_linux.go b/client_linux.go index 509dfa0..7aa39d1 100644 --- a/client_linux.go +++ b/client_linux.go @@ -73,12 +73,8 @@ func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) { ae := netlink.NewAttributeEncoder() ae.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus) ae.String(unix.DEVLINK_ATTR_DEV_NAME, dev.Device) - data, err := ae.Encode() - if err != nil { - return nil, err - } - msgs, err := c.execute(unix.DEVLINK_CMD_DPIPE_TABLE_GET, netlink.Acknowledge, data) + msgs, err := c.execute(unix.DEVLINK_CMD_DPIPE_TABLE_GET, netlink.Acknowledge, ae) if err != nil { return nil, err } @@ -88,7 +84,15 @@ func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) { // execute executes the specified command with additional header flags. The // netlink.Request header flag is automatically set. -func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, data []byte) ([]genetlink.Message, error) { +func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, ae *netlink.AttributeEncoder) ([]genetlink.Message, error) { + var data []byte + var err error + if ae != nil { + data, err = ae.Encode() + if err != nil { + return nil, err + } + } return c.c.Execute( genetlink.Message{ Header: genetlink.Header{ From 8e7e7322efa358633218c6de499954c184a3d963 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sun, 10 Nov 2019 21:31:42 +0300 Subject: [PATCH 7/9] devlink: use ad.Nested() to parse nested netlink attributes for DPIPE Signed-off-by: Alexander Petrovskiy --- client_linux.go | 52 +++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/client_linux.go b/client_linux.go index 7aa39d1..be45634 100644 --- a/client_linux.go +++ b/client_linux.go @@ -190,37 +190,39 @@ func parseDPIPETables(msgs []genetlink.Message) ([]*DPIPETable, error) { case unix.DEVLINK_ATTR_DEV_NAME: dev = ad.String() case unix.DEVLINK_ATTR_DPIPE_TABLES: - tablesData := ad.Bytes() - adTables, err := netlink.NewAttributeDecoder(tablesData) - if err != nil { - continue - } - for adTables.Next() { - if adTables.Type() == unix.DEVLINK_ATTR_DPIPE_TABLE { - tableData := adTables.Bytes() - adTable, err := netlink.NewAttributeDecoder(tableData) - if err != nil { - continue - } - var t DPIPETable + // Netlink array of DPIPE tables. + ad.Nested(func(nad *netlink.AttributeDecoder) error { + for nad.Next() { + t := parseDPIPETable(nad) t.Bus = bus t.Device = dev - for adTable.Next() { - switch adTable.Type() { - case unix.DEVLINK_ATTR_DPIPE_TABLE_NAME: - t.Name = adTable.String() - case unix.DEVLINK_ATTR_DPIPE_TABLE_SIZE: - t.Size = adTable.Uint64() - case unix.DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED: - t.CountersEnabled = adTable.Uint8() != 0 - } - } - ts = append(ts, &t) + ts = append(ts, t) } - } + return nil + }) } } } return ts, nil } + +// parseDPIPETable parses a single DPIPE table from a netlink attribute payload. +func parseDPIPETable(ad *netlink.AttributeDecoder) *DPIPETable { + var t DPIPETable + ad.Nested(func(nad *netlink.AttributeDecoder) error { + // Netlink entry for a single DPIPE table. + for nad.Next() { + switch nad.Type() { + case unix.DEVLINK_ATTR_DPIPE_TABLE_NAME: + t.Name = nad.String() + case unix.DEVLINK_ATTR_DPIPE_TABLE_SIZE: + t.Size = nad.Uint64() + case unix.DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED: + t.CountersEnabled = nad.Uint8() != 0 + } + } + return nil + }) + return &t +} From e1ed50972eb306cb9d8f3df39dda2b64e7d016f9 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sun, 10 Nov 2019 21:35:41 +0300 Subject: [PATCH 8/9] devlink: check for errors at the end of ad.Next() loop Signed-off-by: Alexander Petrovskiy --- client_linux.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client_linux.go b/client_linux.go index be45634..ee10bd0 100644 --- a/client_linux.go +++ b/client_linux.go @@ -191,6 +191,7 @@ func parseDPIPETables(msgs []genetlink.Message) ([]*DPIPETable, error) { dev = ad.String() case unix.DEVLINK_ATTR_DPIPE_TABLES: // Netlink array of DPIPE tables. + // Errors while parsing are propagated up to top-level ad.Err check. ad.Nested(func(nad *netlink.AttributeDecoder) error { for nad.Next() { t := parseDPIPETable(nad) @@ -203,6 +204,9 @@ func parseDPIPETables(msgs []genetlink.Message) ([]*DPIPETable, error) { } } + if err := ad.Err(); err != nil { + return nil, err + } } return ts, nil } From 3b3059ac9cb7415ce54e5191f5c17fbc62a2ec89 Mon Sep 17 00:00:00 2001 From: Alexander Petrovskiy Date: Sun, 10 Nov 2019 21:53:22 +0300 Subject: [PATCH 9/9] devlink: relax integration test for DPIPE tables Just print all DPIPE tables that are available. Signed-off-by: Alexander Petrovskiy --- client_linux_integration_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client_linux_integration_test.go b/client_linux_integration_test.go index 51b35f7..796743e 100644 --- a/client_linux_integration_test.go +++ b/client_linux_integration_test.go @@ -59,10 +59,7 @@ func TestLinuxClientIntegration(t *testing.T) { tables = append(tables, tt...) } - if len(tables) == 0 { - t.Fatalf("failed to retrieve any DPIPE tables from devices") - } - + // Just print all DPIPE tables that are available. for _, table := range tables { t.Logf("dpipe_table: %+v", table) }