From 2abd27ecbc31d74bacc5e1fae9a17ea79fa4c7d2 Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 21:17:52 +0530 Subject: [PATCH 01/18] test 1 --- ovsnl/client_linux_integration_test.go | 87 ++++++++++++++++++++++++ ovsnl/datapath_linux_integration_test.go | 52 ++++++++++++++ ovsnl/datapath_linux_test.go | 1 + 3 files changed, 140 insertions(+) create mode 100644 ovsnl/datapath_linux_integration_test.go diff --git a/ovsnl/client_linux_integration_test.go b/ovsnl/client_linux_integration_test.go index 4e385d8..4977dd6 100644 --- a/ovsnl/client_linux_integration_test.go +++ b/ovsnl/client_linux_integration_test.go @@ -18,15 +18,30 @@ package ovsnl_test import ( + "fmt" "net" "os" "testing" "github.com/digitalocean/go-openvswitch/ovsnl" + "github.com/digitalocean/go-openvswitch/ovsnl/internal/ovsh" "github.com/google/go-cmp/cmp" + "github.com/mdlayher/genetlink" + "github.com/mdlayher/netlink" + "github.com/mdlayher/netlink/nlenc" + "golang.org/x/sys/unix" ) func TestLinuxClientIntegration(t *testing.T) { + + // Skip this test in CI or other automated environments where the OVS + // kernel/netlink families are not present. GitHub Actions (and many CI + // runners) set CI=true; additionally users can set SKIP_LIVE_TESTS=1 to + // force skipping locally. + // if os.Getenv("CI") == "true" || os.Getenv("SKIP_LIVE_TESTS") == "1" { + // t.Skip("Skipping live OVS integration tests in CI / SKIP_LIVE_TESTS environment") + // } + c, err := ovsnl.New() if err != nil { if os.IsNotExist(err) { @@ -68,3 +83,75 @@ func testClientDatapath(t *testing.T, c *ovsnl.Client, datapath string) { t.Fatalf("unexpected datapath name (-want +got):\n%s", diff) } } + +// func TestClientDatapathListShortHeader(t *testing.T) { +// conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { +// // Not enough data for ovsh.Header - this should trigger an error +// return []genetlink.Message{ +// { +// Data: []byte{0xff, 0xff}, // Only 2 bytes, but header needs more +// }, +// }, nil +// })) + +// c, err := ovsnl.New() +// if err != nil { +// t.Fatalf("failed to create client: %v", err) +// } +// defer c.Close() + +// _, err = c.Datapath.List() +// if err == nil { +// t.Fatalf("expected an error due to short header, but none occurred") +// } + +// t.Logf("OK error: %v", err) +// } + +// ovsFamilies creates a test handler that returns OVS family messages +func ovsFamilies(handler func(genetlink.Message, netlink.Message) ([]genetlink.Message, error)) func(genetlink.Message, netlink.Message) ([]genetlink.Message, error) { + return func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { + // Handle family listing requests + if greq.Header.Command == unix.CTRL_CMD_GETFAMILY { + return familyMessages([]string{ + ovsh.DatapathFamily, + }), nil + } + + // Handle actual datapath requests + return handler(greq, nreq) + } +} + +func familyMessages(families []string) []genetlink.Message { + msgs := make([]genetlink.Message, 0, len(families)) + + var id uint16 + for _, f := range families { + msgs = append(msgs, genetlink.Message{ + Data: mustMarshalAttributes([]netlink.Attribute{ + { + Type: unix.CTRL_ATTR_FAMILY_ID, + Data: nlenc.Uint16Bytes(id), + }, + { + Type: unix.CTRL_ATTR_FAMILY_NAME, + Data: nlenc.Bytes(f), + }, + }), + }) + + id++ + } + + return msgs +} + +func mustMarshalAttributes(attrs []netlink.Attribute) []byte { + b, err := netlink.MarshalAttributes(attrs) + if err != nil { + panic(fmt.Sprintf("failed to marshal attributes: %v", err)) + } + + return b +} diff --git a/ovsnl/datapath_linux_integration_test.go b/ovsnl/datapath_linux_integration_test.go new file mode 100644 index 0000000..2c89b46 --- /dev/null +++ b/ovsnl/datapath_linux_integration_test.go @@ -0,0 +1,52 @@ +// Copyright 2017 DigitalOcean. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux && integration +// +build linux,integration + +package ovsnl + +import ( + "testing" +) + +// TestClientDatapathListIntegration tests datapath listing with real Open vSwitch +func TestClientDatapathListIntegration(t *testing.T) { + // Skip if not running in integration test environment + if testing.Short() { + t.Skip("skipping integration test") + } + + c, err := NewClient() + if err != nil { + t.Skipf("skipping integration test: %v", err) + } + defer c.Close() + + dps, err := c.Datapath.List() + if err != nil { + t.Fatalf("failed to list datapaths: %v", err) + } + + if len(dps) == 0 { + t.Log("no datapaths found (Open vSwitch may not be running)") + return + } + + // Verify we can list datapaths + t.Logf("found %d datapaths", len(dps)) + for _, dp := range dps { + t.Logf("datapath: %s (index: %d)", dp.Name, dp.Index) + } +} diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index f5f7694..3074915 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -33,6 +33,7 @@ import ( func TestClientDatapathListShortHeader(t *testing.T) { conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { // Not enough data for ovsh.Header. + t.Logf("Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) return []genetlink.Message{ { Data: []byte{0xff, 0xff}, From fb34463bb2a68d72ba2e81ef28f4db5067ccaf35 Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 21:23:05 +0530 Subject: [PATCH 02/18] test 2 --- ovsnl/client_linux_integration_test.go | 90 ++++++++++++-------------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/ovsnl/client_linux_integration_test.go b/ovsnl/client_linux_integration_test.go index 4977dd6..c3762be 100644 --- a/ovsnl/client_linux_integration_test.go +++ b/ovsnl/client_linux_integration_test.go @@ -18,18 +18,12 @@ package ovsnl_test import ( - "fmt" "net" "os" "testing" "github.com/digitalocean/go-openvswitch/ovsnl" - "github.com/digitalocean/go-openvswitch/ovsnl/internal/ovsh" "github.com/google/go-cmp/cmp" - "github.com/mdlayher/genetlink" - "github.com/mdlayher/netlink" - "github.com/mdlayher/netlink/nlenc" - "golang.org/x/sys/unix" ) func TestLinuxClientIntegration(t *testing.T) { @@ -109,49 +103,49 @@ func testClientDatapath(t *testing.T, c *ovsnl.Client, datapath string) { // } // ovsFamilies creates a test handler that returns OVS family messages -func ovsFamilies(handler func(genetlink.Message, netlink.Message) ([]genetlink.Message, error)) func(genetlink.Message, netlink.Message) ([]genetlink.Message, error) { - return func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { - // Handle family listing requests - if greq.Header.Command == unix.CTRL_CMD_GETFAMILY { - return familyMessages([]string{ - ovsh.DatapathFamily, - }), nil - } - - // Handle actual datapath requests - return handler(greq, nreq) - } -} +// func ovsFamilies(handler func(genetlink.Message, netlink.Message) ([]genetlink.Message, error)) func(genetlink.Message, netlink.Message) ([]genetlink.Message, error) { +// return func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { +// // Handle family listing requests +// if greq.Header.Command == unix.CTRL_CMD_GETFAMILY { +// return familyMessages([]string{ +// ovsh.DatapathFamily, +// }), nil +// } + +// // Handle actual datapath requests +// return handler(greq, nreq) +// } +// } -func familyMessages(families []string) []genetlink.Message { - msgs := make([]genetlink.Message, 0, len(families)) - - var id uint16 - for _, f := range families { - msgs = append(msgs, genetlink.Message{ - Data: mustMarshalAttributes([]netlink.Attribute{ - { - Type: unix.CTRL_ATTR_FAMILY_ID, - Data: nlenc.Uint16Bytes(id), - }, - { - Type: unix.CTRL_ATTR_FAMILY_NAME, - Data: nlenc.Bytes(f), - }, - }), - }) - - id++ - } +// func familyMessages(families []string) []genetlink.Message { +// msgs := make([]genetlink.Message, 0, len(families)) + +// var id uint16 +// for _, f := range families { +// msgs = append(msgs, genetlink.Message{ +// Data: mustMarshalAttributes([]netlink.Attribute{ +// { +// Type: unix.CTRL_ATTR_FAMILY_ID, +// Data: nlenc.Uint16Bytes(id), +// }, +// { +// Type: unix.CTRL_ATTR_FAMILY_NAME, +// Data: nlenc.Bytes(f), +// }, +// }), +// }) + +// id++ +// } - return msgs -} +// return msgs +// } -func mustMarshalAttributes(attrs []netlink.Attribute) []byte { - b, err := netlink.MarshalAttributes(attrs) - if err != nil { - panic(fmt.Sprintf("failed to marshal attributes: %v", err)) - } +// func mustMarshalAttributes(attrs []netlink.Attribute) []byte { +// b, err := netlink.MarshalAttributes(attrs) +// if err != nil { +// panic(fmt.Sprintf("failed to marshal attributes: %v", err)) +// } - return b -} +// return b +// } From ab4af645efc28707c3bcd52bcc64cd348ada8455 Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 21:33:10 +0530 Subject: [PATCH 03/18] test 2 --- ovsnl/datapath_linux_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index 3074915..d86534c 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -33,7 +33,8 @@ import ( func TestClientDatapathListShortHeader(t *testing.T) { conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { // Not enough data for ovsh.Header. - t.Logf("Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) + t.Logf("🔍 Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) + t.Logf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) return []genetlink.Message{ { Data: []byte{0xff, 0xff}, From 167d814c12d5227443732e80625541318e6541e5 Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 21:58:41 +0530 Subject: [PATCH 04/18] test 3 --- ovsnl/datapath_linux_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index d86534c..bce8fe7 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -18,6 +18,7 @@ package ovsnl import ( + "fmt" "testing" "unsafe" @@ -35,6 +36,7 @@ func TestClientDatapathListShortHeader(t *testing.T) { // Not enough data for ovsh.Header. t.Logf("🔍 Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) t.Logf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) + fmt.Printf("🔍 Mock called with command: %d, data length: %d\n", greq.Header.Command, len(greq.Data)) return []genetlink.Message{ { Data: []byte{0xff, 0xff}, @@ -233,6 +235,7 @@ func ovsFamilies(handler func(genetlink.Message, netlink.Message) ([]genetlink.M } // Handle actual datapath requests + fmt.Printf("🔍 ovsFamilies: calling handler for command %d\n", greq.Header.Command) return handler(greq, nreq) } } From 2b1d21774202b78c2848a396241f30879bed6eab Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 22:12:29 +0530 Subject: [PATCH 05/18] test 4 --- ovsnl/datapath_linux_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index bce8fe7..08593db 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -34,8 +34,8 @@ import ( func TestClientDatapathListShortHeader(t *testing.T) { conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { // Not enough data for ovsh.Header. - t.Logf("🔍 Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) - t.Logf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) + fmt.Printf("🔍 Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) + fmt.Printf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) fmt.Printf("🔍 Mock called with command: %d, data length: %d\n", greq.Header.Command, len(greq.Data)) return []genetlink.Message{ { From d689770ad00631c8125e0b020ef8fd67faaf1a8a Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 22:18:57 +0530 Subject: [PATCH 06/18] test 5 --- ovsnl/datapath_linux_test.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index 08593db..cb14ca2 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -34,12 +34,9 @@ import ( func TestClientDatapathListShortHeader(t *testing.T) { conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { // Not enough data for ovsh.Header. - fmt.Printf("🔍 Mock called with command: %d, data length: %d", greq.Header.Command, len(greq.Data)) - fmt.Printf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) - fmt.Printf("🔍 Mock called with command: %d, data length: %d\n", greq.Header.Command, len(greq.Data)) return []genetlink.Message{ { - Data: []byte{0xff, 0xff}, + Data: []byte{0xff, 0xff}, // Only 2 bytes, but sizeofHeader is 4 }, }, nil })) @@ -50,6 +47,13 @@ func TestClientDatapathListShortHeader(t *testing.T) { } defer c.Close() + if c.Datapath == nil { + t.Fatalf("Datapath service is nil - mock not properly initialized") + } + + t.Logf("🔍 About to call c.Datapath.List()") + fmt.Printf("🔍 About to call c.Datapath.List()\n") + _, err = c.Datapath.List() if err == nil { t.Fatalf("expected an error, but none occurred") @@ -63,12 +67,12 @@ func TestClientDatapathListBadStats(t *testing.T) { // Valid header; not enough data for ovsh.DPStats. return []genetlink.Message{{ Data: append( - // ovsh.Header. + // ovsh.Header (4 bytes). []byte{0xff, 0xff, 0xff, 0xff}, // netlink attributes. mustMarshalAttributes([]netlink.Attribute{{ Type: ovsh.DpAttrStats, - Data: []byte{0xff}, + Data: []byte{0xff}, // Only 1 byte, but sizeofDPStats is 32 bytes }})..., ), }}, nil @@ -93,12 +97,12 @@ func TestClientDatapathListBadMegaflowStats(t *testing.T) { // Valid header; not enough data for ovsh.DPMegaflowStats. return []genetlink.Message{{ Data: append( - // ovsh.Header. + // ovsh.Header (4 bytes). []byte{0xff, 0xff, 0xff, 0xff}, // netlink attributes. mustMarshalAttributes([]netlink.Attribute{{ Type: ovsh.DpAttrMegaflowStats, - Data: []byte{0xff}, + Data: []byte{0xff}, // Only 1 byte, but sizeofDPMegaflowStats is 32 bytes }})..., ), }}, nil From 5a3c866d2cd408d28c7a0996f4bdddca4b96d835 Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Mon, 6 Oct 2025 22:28:06 +0530 Subject: [PATCH 07/18] test 6 --- ovsnl/client.go | 1 + ovsnl/datapath.go | 2 ++ ovsnl/datapath_linux_test.go | 7 +------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ovsnl/client.go b/ovsnl/client.go index 0e3fb5b..dab5e58 100644 --- a/ovsnl/client.go +++ b/ovsnl/client.go @@ -145,6 +145,7 @@ func headerBytes(h ovsh.Header) []byte { func parseHeader(b []byte) (ovsh.Header, error) { // Verify that the byte slice is long enough before doing unsafe casts. if l := len(b); l < sizeofHeader { + fmt.Printf("🔍 parseHeader: not enough data - got %d bytes, need %d\n", l, sizeofHeader) return ovsh.Header{}, fmt.Errorf("not enough data for OVS message header: %d bytes", l) } diff --git a/ovsnl/datapath.go b/ovsnl/datapath.go index 6751cde..83b2713 100644 --- a/ovsnl/datapath.go +++ b/ovsnl/datapath.go @@ -170,6 +170,7 @@ func parseDPStats(b []byte) (DatapathStats, error) { // Verify that the byte slice is the correct length before doing // unsafe casts. if want, got := sizeofDPStats, len(b); want != got { + fmt.Printf("🔍 parseDPStats: wrong size - got %d bytes, need %d\n", got, want) return DatapathStats{}, fmt.Errorf("unexpected datapath stats structure size, want %d, got %d", want, got) } @@ -187,6 +188,7 @@ func parseDPMegaflowStats(b []byte) (DatapathMegaflowStats, error) { // Verify that the byte slice is the correct length before doing // unsafe casts. if want, got := sizeofDPMegaflowStats, len(b); want != got { + fmt.Printf("🔍 parseDPMegaflowStats: wrong size - got %d bytes, need %d\n", got, want) return DatapathMegaflowStats{}, fmt.Errorf("unexpected datapath megaflow stats structure size, want %d, got %d", want, got) } diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index cb14ca2..78cd180 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -34,6 +34,7 @@ import ( func TestClientDatapathListShortHeader(t *testing.T) { conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { // Not enough data for ovsh.Header. + t.Logf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) return []genetlink.Message{ { Data: []byte{0xff, 0xff}, // Only 2 bytes, but sizeofHeader is 4 @@ -47,13 +48,7 @@ func TestClientDatapathListShortHeader(t *testing.T) { } defer c.Close() - if c.Datapath == nil { - t.Fatalf("Datapath service is nil - mock not properly initialized") - } - t.Logf("🔍 About to call c.Datapath.List()") - fmt.Printf("🔍 About to call c.Datapath.List()\n") - _, err = c.Datapath.List() if err == nil { t.Fatalf("expected an error, but none occurred") From fbd5e05261d1fde377822682f8271c8d5cfbd22b Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Tue, 7 Oct 2025 14:13:21 +0530 Subject: [PATCH 08/18] test 7 --- ovsnl/client.go | 1 - ovsnl/datapath.go | 2 -- ovsnl/datapath_linux_integration_test.go | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ovsnl/client.go b/ovsnl/client.go index dab5e58..0e3fb5b 100644 --- a/ovsnl/client.go +++ b/ovsnl/client.go @@ -145,7 +145,6 @@ func headerBytes(h ovsh.Header) []byte { func parseHeader(b []byte) (ovsh.Header, error) { // Verify that the byte slice is long enough before doing unsafe casts. if l := len(b); l < sizeofHeader { - fmt.Printf("🔍 parseHeader: not enough data - got %d bytes, need %d\n", l, sizeofHeader) return ovsh.Header{}, fmt.Errorf("not enough data for OVS message header: %d bytes", l) } diff --git a/ovsnl/datapath.go b/ovsnl/datapath.go index 83b2713..6751cde 100644 --- a/ovsnl/datapath.go +++ b/ovsnl/datapath.go @@ -170,7 +170,6 @@ func parseDPStats(b []byte) (DatapathStats, error) { // Verify that the byte slice is the correct length before doing // unsafe casts. if want, got := sizeofDPStats, len(b); want != got { - fmt.Printf("🔍 parseDPStats: wrong size - got %d bytes, need %d\n", got, want) return DatapathStats{}, fmt.Errorf("unexpected datapath stats structure size, want %d, got %d", want, got) } @@ -188,7 +187,6 @@ func parseDPMegaflowStats(b []byte) (DatapathMegaflowStats, error) { // Verify that the byte slice is the correct length before doing // unsafe casts. if want, got := sizeofDPMegaflowStats, len(b); want != got { - fmt.Printf("🔍 parseDPMegaflowStats: wrong size - got %d bytes, need %d\n", got, want) return DatapathMegaflowStats{}, fmt.Errorf("unexpected datapath megaflow stats structure size, want %d, got %d", want, got) } diff --git a/ovsnl/datapath_linux_integration_test.go b/ovsnl/datapath_linux_integration_test.go index 2c89b46..df8bca1 100644 --- a/ovsnl/datapath_linux_integration_test.go +++ b/ovsnl/datapath_linux_integration_test.go @@ -28,7 +28,7 @@ func TestClientDatapathListIntegration(t *testing.T) { t.Skip("skipping integration test") } - c, err := NewClient() + c, err := New() if err != nil { t.Skipf("skipping integration test: %v", err) } From 96bd572a68a489f8393dc4dbcca289501f70b00a Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Tue, 7 Oct 2025 15:01:44 +0530 Subject: [PATCH 09/18] test 8 --- ovsnl/datapath_linux_test.go | 57 +++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index 78cd180..a8a0297 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -49,8 +49,12 @@ func TestClientDatapathListShortHeader(t *testing.T) { defer c.Close() t.Logf("🔍 About to call c.Datapath.List()") - _, err = c.Datapath.List() + dps, err := c.Datapath.List() if err == nil { + t.Logf("🔍 DEBUG: No error occurred, got %d datapaths", len(dps)) + for i, dp := range dps { + t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q", i, dp.Index, dp.Name) + } t.Fatalf("expected an error, but none occurred") } @@ -117,6 +121,49 @@ func TestClientDatapathListBadMegaflowStats(t *testing.T) { t.Logf("OK error: %v", err) } +func TestClientDatapathListDebug(t *testing.T) { + conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { + // Return some test data to see what we get + return []genetlink.Message{ + { + Data: append( + // ovsh.Header (4 bytes). + []byte{0x01, 0x02, 0x03, 0x04}, + // netlink attributes. + mustMarshalAttributes([]netlink.Attribute{ + { + Type: ovsh.DpAttrName, + Data: nlenc.Bytes("test-datapath"), + }, + { + Type: ovsh.DpAttrUserFeatures, + Data: nlenc.Uint32Bytes(0x03), // Some features + }, + })..., + ), + }, + }, nil + })) + + c, err := newTestClient(conn) + if err != nil { + t.Fatalf("failed to create client: %v", err) + } + defer c.Close() + + dps, err := c.Datapath.List() + if err != nil { + t.Fatalf("failed to list datapaths: %v", err) + } + + t.Logf("🔍 DEBUG: Got %d datapaths", len(dps)) + for i, dp := range dps { + t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q, Features=%s", i, dp.Index, dp.Name, dp.Features) + t.Logf("🔍 DEBUG: Stats: Hit=%d, Missed=%d, Lost=%d, Flows=%d", dp.Stats.Hit, dp.Stats.Missed, dp.Stats.Lost, dp.Stats.Flows) + t.Logf("🔍 DEBUG: MegaflowStats: MaskHits=%d, Masks=%d", dp.MegaflowStats.MaskHits, dp.MegaflowStats.Masks) + } +} + func TestClientDatapathListOK(t *testing.T) { system := Datapath{ Name: "ovs-system", @@ -168,6 +215,14 @@ func TestClientDatapathListOK(t *testing.T) { t.Fatalf("failed to list datapaths: %v", err) } + // Debug: Print what we actually got + t.Logf("🔍 DEBUG: Got %d datapaths", len(dps)) + for i, dp := range dps { + t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q, Features=%s", i, dp.Index, dp.Name, dp.Features) + t.Logf("🔍 DEBUG: Stats: Hit=%d, Missed=%d, Lost=%d, Flows=%d", dp.Stats.Hit, dp.Stats.Missed, dp.Stats.Lost, dp.Stats.Flows) + t.Logf("🔍 DEBUG: MegaflowStats: MaskHits=%d, Masks=%d", dp.MegaflowStats.MaskHits, dp.MegaflowStats.Masks) + } + if diff := cmp.Diff(1, len(dps)); diff != "" { t.Fatalf("unexpected number of datapaths (-want +got):\n%s", diff) } From 30a437ef15925792c1abeccf86fcdf48611fcd4e Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Tue, 7 Oct 2025 22:02:04 +0530 Subject: [PATCH 10/18] test 9 --- ovsnl/client_linux_integration_test.go | 80 ----------------- ovsnl/client_linux_test.go | 11 +-- ovsnl/datapath_linux_test.go | 120 ++++++++++++------------- 3 files changed, 63 insertions(+), 148 deletions(-) diff --git a/ovsnl/client_linux_integration_test.go b/ovsnl/client_linux_integration_test.go index c3762be..14c42d3 100644 --- a/ovsnl/client_linux_integration_test.go +++ b/ovsnl/client_linux_integration_test.go @@ -28,14 +28,6 @@ import ( func TestLinuxClientIntegration(t *testing.T) { - // Skip this test in CI or other automated environments where the OVS - // kernel/netlink families are not present. GitHub Actions (and many CI - // runners) set CI=true; additionally users can set SKIP_LIVE_TESTS=1 to - // force skipping locally. - // if os.Getenv("CI") == "true" || os.Getenv("SKIP_LIVE_TESTS") == "1" { - // t.Skip("Skipping live OVS integration tests in CI / SKIP_LIVE_TESTS environment") - // } - c, err := ovsnl.New() if err != nil { if os.IsNotExist(err) { @@ -77,75 +69,3 @@ func testClientDatapath(t *testing.T, c *ovsnl.Client, datapath string) { t.Fatalf("unexpected datapath name (-want +got):\n%s", diff) } } - -// func TestClientDatapathListShortHeader(t *testing.T) { -// conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { -// // Not enough data for ovsh.Header - this should trigger an error -// return []genetlink.Message{ -// { -// Data: []byte{0xff, 0xff}, // Only 2 bytes, but header needs more -// }, -// }, nil -// })) - -// c, err := ovsnl.New() -// if err != nil { -// t.Fatalf("failed to create client: %v", err) -// } -// defer c.Close() - -// _, err = c.Datapath.List() -// if err == nil { -// t.Fatalf("expected an error due to short header, but none occurred") -// } - -// t.Logf("OK error: %v", err) -// } - -// ovsFamilies creates a test handler that returns OVS family messages -// func ovsFamilies(handler func(genetlink.Message, netlink.Message) ([]genetlink.Message, error)) func(genetlink.Message, netlink.Message) ([]genetlink.Message, error) { -// return func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { -// // Handle family listing requests -// if greq.Header.Command == unix.CTRL_CMD_GETFAMILY { -// return familyMessages([]string{ -// ovsh.DatapathFamily, -// }), nil -// } - -// // Handle actual datapath requests -// return handler(greq, nreq) -// } -// } - -// func familyMessages(families []string) []genetlink.Message { -// msgs := make([]genetlink.Message, 0, len(families)) - -// var id uint16 -// for _, f := range families { -// msgs = append(msgs, genetlink.Message{ -// Data: mustMarshalAttributes([]netlink.Attribute{ -// { -// Type: unix.CTRL_ATTR_FAMILY_ID, -// Data: nlenc.Uint16Bytes(id), -// }, -// { -// Type: unix.CTRL_ATTR_FAMILY_NAME, -// Data: nlenc.Bytes(f), -// }, -// }), -// }) - -// id++ -// } - -// return msgs -// } - -// func mustMarshalAttributes(attrs []netlink.Attribute) []byte { -// b, err := netlink.MarshalAttributes(attrs) -// if err != nil { -// panic(fmt.Sprintf("failed to marshal attributes: %v", err)) -// } - -// return b -// } diff --git a/ovsnl/client_linux_test.go b/ovsnl/client_linux_test.go index 16bb7a9..a7fc4d3 100644 --- a/ovsnl/client_linux_test.go +++ b/ovsnl/client_linux_test.go @@ -30,12 +30,10 @@ import ( "golang.org/x/sys/unix" ) -// newTestClient creates a test client with a mock genetlink connection func newTestClient(conn *genetlink.Conn) (*Client, error) { c := &Client{} c.c = conn - // Initialize services. families, err := c.c.ListFamilies() if err != nil { return nil, err @@ -45,10 +43,13 @@ func newTestClient(conn *genetlink.Conn) (*Client, error) { return nil, err } - // For testing, we'll skip the aggregator initialization - // since it requires actual kernel conntrack support - c.Agg = nil + // ✅ Inject our mock connection directly into the datapath service + if c.Datapath != nil { + c.Datapath.c = c + c.c = conn + } + c.Agg = nil return c, nil } diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index a8a0297..d484537 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -18,7 +18,6 @@ package ovsnl import ( - "fmt" "testing" "unsafe" @@ -33,13 +32,17 @@ import ( func TestClientDatapathListShortHeader(t *testing.T) { conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { - // Not enough data for ovsh.Header. - t.Logf("🔍 Mock returning short data: %v", []byte{0xff, 0xff}) - return []genetlink.Message{ - { - Data: []byte{0xff, 0xff}, // Only 2 bytes, but sizeofHeader is 4 - }, - }, nil + + // Check if this is the datapath list command + if greq.Header.Command == ovsh.DpCmdGet { + // Return deliberately short data for datapath list + shortData := []byte{0xff, 0xff} + return []genetlink.Message{ + {Data: shortData}, + }, nil + } + + return []genetlink.Message{}, nil })) c, err := newTestClient(conn) @@ -48,7 +51,6 @@ func TestClientDatapathListShortHeader(t *testing.T) { } defer c.Close() - t.Logf("🔍 About to call c.Datapath.List()") dps, err := c.Datapath.List() if err == nil { t.Logf("🔍 DEBUG: No error occurred, got %d datapaths", len(dps)) @@ -121,48 +123,48 @@ func TestClientDatapathListBadMegaflowStats(t *testing.T) { t.Logf("OK error: %v", err) } -func TestClientDatapathListDebug(t *testing.T) { - conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { - // Return some test data to see what we get - return []genetlink.Message{ - { - Data: append( - // ovsh.Header (4 bytes). - []byte{0x01, 0x02, 0x03, 0x04}, - // netlink attributes. - mustMarshalAttributes([]netlink.Attribute{ - { - Type: ovsh.DpAttrName, - Data: nlenc.Bytes("test-datapath"), - }, - { - Type: ovsh.DpAttrUserFeatures, - Data: nlenc.Uint32Bytes(0x03), // Some features - }, - })..., - ), - }, - }, nil - })) - - c, err := newTestClient(conn) - if err != nil { - t.Fatalf("failed to create client: %v", err) - } - defer c.Close() - - dps, err := c.Datapath.List() - if err != nil { - t.Fatalf("failed to list datapaths: %v", err) - } - - t.Logf("🔍 DEBUG: Got %d datapaths", len(dps)) - for i, dp := range dps { - t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q, Features=%s", i, dp.Index, dp.Name, dp.Features) - t.Logf("🔍 DEBUG: Stats: Hit=%d, Missed=%d, Lost=%d, Flows=%d", dp.Stats.Hit, dp.Stats.Missed, dp.Stats.Lost, dp.Stats.Flows) - t.Logf("🔍 DEBUG: MegaflowStats: MaskHits=%d, Masks=%d", dp.MegaflowStats.MaskHits, dp.MegaflowStats.Masks) - } -} +// func TestClientDatapathListDebug(t *testing.T) { +// conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { +// // Return some test data to see what we get +// return []genetlink.Message{ +// { +// Data: append( +// // ovsh.Header (4 bytes). +// []byte{0x01, 0x02, 0x03, 0x04}, +// // netlink attributes. +// mustMarshalAttributes([]netlink.Attribute{ +// { +// Type: ovsh.DpAttrName, +// Data: nlenc.Bytes("test-datapath"), +// }, +// { +// Type: ovsh.DpAttrUserFeatures, +// Data: nlenc.Uint32Bytes(0x03), // Some features +// }, +// })..., +// ), +// }, +// }, nil +// })) + +// c, err := newTestClient(conn) +// if err != nil { +// t.Fatalf("failed to create client: %v", err) +// } +// defer c.Close() + +// dps, err := c.Datapath.List() +// if err != nil { +// t.Fatalf("failed to list datapaths: %v", err) +// } + +// t.Logf("🔍 DEBUG: Got %d datapaths", len(dps)) +// for i, dp := range dps { +// t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q, Features=%s", i, dp.Index, dp.Name, dp.Features) +// t.Logf("🔍 DEBUG: Stats: Hit=%d, Missed=%d, Lost=%d, Flows=%d", dp.Stats.Hit, dp.Stats.Missed, dp.Stats.Lost, dp.Stats.Flows) +// t.Logf("🔍 DEBUG: MegaflowStats: MaskHits=%d, Masks=%d", dp.MegaflowStats.MaskHits, dp.MegaflowStats.Masks) +// } +// } func TestClientDatapathListOK(t *testing.T) { system := Datapath{ @@ -215,14 +217,6 @@ func TestClientDatapathListOK(t *testing.T) { t.Fatalf("failed to list datapaths: %v", err) } - // Debug: Print what we actually got - t.Logf("🔍 DEBUG: Got %d datapaths", len(dps)) - for i, dp := range dps { - t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q, Features=%s", i, dp.Index, dp.Name, dp.Features) - t.Logf("🔍 DEBUG: Stats: Hit=%d, Missed=%d, Lost=%d, Flows=%d", dp.Stats.Hit, dp.Stats.Missed, dp.Stats.Lost, dp.Stats.Flows) - t.Logf("🔍 DEBUG: MegaflowStats: MaskHits=%d, Masks=%d", dp.MegaflowStats.MaskHits, dp.MegaflowStats.Masks) - } - if diff := cmp.Diff(1, len(dps)); diff != "" { t.Fatalf("unexpected number of datapaths (-want +got):\n%s", diff) } @@ -281,15 +275,15 @@ func mustMarshalDatapath(dp Datapath) []byte { // ovsFamilies creates a test handler that returns OVS family messages func ovsFamilies(handler func(genetlink.Message, netlink.Message) ([]genetlink.Message, error)) func(genetlink.Message, netlink.Message) ([]genetlink.Message, error) { return func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { - // Handle family listing requests - if greq.Header.Command == unix.CTRL_CMD_GETFAMILY { + + // Handle family listing requests (CTRL family) + if nreq.Header.Type == unix.GENL_ID_CTRL && greq.Header.Command == unix.CTRL_CMD_GETFAMILY { return familyMessages([]string{ ovsh.DatapathFamily, }), nil } - // Handle actual datapath requests - fmt.Printf("🔍 ovsFamilies: calling handler for command %d\n", greq.Header.Command) + // Handle actual datapath requests (OVS datapath family) return handler(greq, nreq) } } From 1d33f6265f2bf4616fc79a2cc10db3d75e157eac Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 11:56:24 +0530 Subject: [PATCH 11/18] test 10 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c70c5c2..bd8158b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.24' + go-version: '1.21' - name: OVS setup run: | From 0af127f2ea9c51172e2ce79cd245c9162721cc7e Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 12:02:36 +0530 Subject: [PATCH 12/18] test 12 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bd8158b..37932a0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.21' + go-version: '1.22' - name: OVS setup run: | From 0c2fe0847f6d23878d3be3afd2883dc8960fb35d Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 12:13:41 +0530 Subject: [PATCH 13/18] test 13 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 37932a0..8b562f6 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.22' + go-version: '1.17' - name: OVS setup run: | From e9164f89bcf8c4b45d3f48c376a7c38f3902d653 Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 12:21:09 +0530 Subject: [PATCH 14/18] test 14 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8b562f6..90d609e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.17' + go-version: '1.19' - name: OVS setup run: | From 598436791a6d157ddf08dc6775163e1cd9579b2e Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 12:45:16 +0530 Subject: [PATCH 15/18] test 15 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 90d609e..7097e9e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.20' - name: OVS setup run: | From 1e89e2f12d06879415d78e4ccf871561599156ee Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 12:48:11 +0530 Subject: [PATCH 16/18] test 16 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7097e9e..4ceef49 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.23' - name: OVS setup run: | From 3eb5c79c0baf7c7667f9ae4fc568fb1c99582191 Mon Sep 17 00:00:00 2001 From: shrouti1995 Date: Wed, 8 Oct 2025 13:52:55 +0530 Subject: [PATCH 17/18] Update go.yml --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4ceef49..c70c5c2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.23' + go-version: '1.24' - name: OVS setup run: | From 4fc25df4d264d8b7f53a44509928c0052fc1821b Mon Sep 17 00:00:00 2001 From: sgangopadhyay Date: Wed, 8 Oct 2025 13:58:12 +0530 Subject: [PATCH 18/18] clean up --- ovsnl/client_linux_test.go | 2 +- ovsnl/datapath_linux_test.go | 52 +++--------------------------------- 2 files changed, 4 insertions(+), 50 deletions(-) diff --git a/ovsnl/client_linux_test.go b/ovsnl/client_linux_test.go index a7fc4d3..448e303 100644 --- a/ovsnl/client_linux_test.go +++ b/ovsnl/client_linux_test.go @@ -43,7 +43,7 @@ func newTestClient(conn *genetlink.Conn) (*Client, error) { return nil, err } - // ✅ Inject our mock connection directly into the datapath service + // Inject our mock connection directly into the datapath service if c.Datapath != nil { c.Datapath.c = c c.c = conn diff --git a/ovsnl/datapath_linux_test.go b/ovsnl/datapath_linux_test.go index d484537..39f9df3 100644 --- a/ovsnl/datapath_linux_test.go +++ b/ovsnl/datapath_linux_test.go @@ -51,12 +51,9 @@ func TestClientDatapathListShortHeader(t *testing.T) { } defer c.Close() - dps, err := c.Datapath.List() - if err == nil { - t.Logf("🔍 DEBUG: No error occurred, got %d datapaths", len(dps)) - for i, dp := range dps { - t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q", i, dp.Index, dp.Name) - } + _, errDatapath := c.Datapath.List() + if errDatapath == nil { + t.Fatalf("expected an error, but none occurred") } @@ -123,49 +120,6 @@ func TestClientDatapathListBadMegaflowStats(t *testing.T) { t.Logf("OK error: %v", err) } -// func TestClientDatapathListDebug(t *testing.T) { -// conn := genltest.Dial(ovsFamilies(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) { -// // Return some test data to see what we get -// return []genetlink.Message{ -// { -// Data: append( -// // ovsh.Header (4 bytes). -// []byte{0x01, 0x02, 0x03, 0x04}, -// // netlink attributes. -// mustMarshalAttributes([]netlink.Attribute{ -// { -// Type: ovsh.DpAttrName, -// Data: nlenc.Bytes("test-datapath"), -// }, -// { -// Type: ovsh.DpAttrUserFeatures, -// Data: nlenc.Uint32Bytes(0x03), // Some features -// }, -// })..., -// ), -// }, -// }, nil -// })) - -// c, err := newTestClient(conn) -// if err != nil { -// t.Fatalf("failed to create client: %v", err) -// } -// defer c.Close() - -// dps, err := c.Datapath.List() -// if err != nil { -// t.Fatalf("failed to list datapaths: %v", err) -// } - -// t.Logf("🔍 DEBUG: Got %d datapaths", len(dps)) -// for i, dp := range dps { -// t.Logf("🔍 DEBUG: Datapath[%d]: Index=%d, Name=%q, Features=%s", i, dp.Index, dp.Name, dp.Features) -// t.Logf("🔍 DEBUG: Stats: Hit=%d, Missed=%d, Lost=%d, Flows=%d", dp.Stats.Hit, dp.Stats.Missed, dp.Stats.Lost, dp.Stats.Flows) -// t.Logf("🔍 DEBUG: MegaflowStats: MaskHits=%d, Masks=%d", dp.MegaflowStats.MaskHits, dp.MegaflowStats.Masks) -// } -// } - func TestClientDatapathListOK(t *testing.T) { system := Datapath{ Name: "ovs-system",