diff --git a/sdn_tests/pins_ondatra/tests/BUILD.bazel b/sdn_tests/pins_ondatra/tests/BUILD.bazel index e2915329627..180622d8b5d 100644 --- a/sdn_tests/pins_ondatra/tests/BUILD.bazel +++ b/sdn_tests/pins_ondatra/tests/BUILD.bazel @@ -47,3 +47,61 @@ ondatra_test( "@com_github_openconfig_ondatra//:go_default_library", ], ) + +#lacp time-out test +ondatra_test( + name = "lacp_timeout_test", + srcs = ["lacp_timeout_test.go"], + deps = [ + "//infrastructure/binding:pinsbind", + "//infrastructure/testhelper", + "@com_github_openconfig_ondatra//:go_default_library", + "@com_github_openconfig_ondatra//gnmi", + "@com_github_openconfig_ondatra//gnmi/oc", + "@com_github_pkg_errors//:errors", + ], +) + +# Link event damping tests +ondatra_test( + name = "link_event_damping_test", + srcs = ["link_event_damping_test.go"], + deps = [ + "//infrastructure/binding:pinsbind", + "//infrastructure/testhelper", + "@com_github_openconfig_gnmi//proto/gnmi:gnmi_go_proto", + "@com_github_openconfig_ondatra//:go_default_library", + "@com_github_openconfig_ondatra//gnmi", + "@com_github_openconfig_ondatra//gnmi/oc", + "@com_github_openconfig_ygnmi//ygnmi", + "@com_github_pkg_errors//:errors", + "@org_golang_google_grpc//:go_default_library", + ], +) + +#port debug test +ondatra_test( + name = "port_debug_data_test", + srcs = ["port_debug_data_test.go"], + deps = [ + "//infrastructure/binding:pinsbind", + "//infrastructure/testhelper", + "@com_github_openconfig_ondatra//:go_default_library", + "@com_github_openconfig_ondatra//gnmi", + ], +) + +#module reset test +ondatra_test_suite( + name = "module_reset_test", + srcs = ["module_reset_test.go"], + deps = [ + "//infrastructure/binding:pinsbind", + "//infrastructure/testhelper", + "@com_github_openconfig_gnoi//system:system_go_proto", + "@com_github_openconfig_gnoi//types:types_go_proto", + "@com_github_openconfig_ondatra//:go_default_library", + "@com_github_openconfig_ondatra//gnmi", + "@com_github_openconfig_ondatra//gnmi/oc", + ], +) diff --git a/sdn_tests/pins_ondatra/tests/lacp_timeout_test.go b/sdn_tests/pins_ondatra/tests/lacp_timeout_test.go new file mode 100644 index 00000000000..153a5bf565f --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/lacp_timeout_test.go @@ -0,0 +1,202 @@ +package lacp_timeout_test + +import ( + "crypto/rand" + "math/big" + "testing" + "time" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/binding/pinsbind" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure//testhelper/testhelper" + "github.com/pkg/errors" +) + +// gNMI can cache local state for up to 10 seconds. We therefore set our timeout to a little longer +// to handle any edge cases when verifying state. +const defaultGNMIWait = 15 * time.Second + +// Convert LACP period types to strings for use in parameterized test names. The output should +// follow CamelCase styles. +func lacpPeriodTypeToString(period oc.E_Lacp_LacpPeriodType) string { + if period == oc.Lacp_LacpPeriodType_FAST { + return "Fast" + } else if period == oc.Lacp_LacpPeriodType_SLOW { + return "Slow" + } + return "Unknown" +} + +// Convert LACP activity types to strings for use in parameterized test names. The output should +// follow CamelCase styles. +func lacpActivityTypeToString(activity oc.E_Lacp_LacpActivityType) string { + if activity == oc.Lacp_LacpActivityType_ACTIVE { + return "Active" + } else if activity == oc.Lacp_LacpActivityType_PASSIVE { + return "Passive" + } + return "Unknown" +} + +// Check if the number of packets is within an acceptable range for 1 minute given the LACP Interval. +func acceptableLACPDUPacketCountForOneMinute(period oc.E_Lacp_LacpPeriodType, count uint64) error { + switch period { + case oc.Lacp_LacpPeriodType_FAST: + // When the period is FAST we expect around 1 packet per-second. So ~60 packets. + if count < 55 || count > 65 { + return errors.Errorf("outside range [55, 65]: %v.", count) + } + case oc.Lacp_LacpPeriodType_SLOW: + // When the period is SLOW we expect around 1 packet every 30 seconds. So ~2 packets. + if count < 1 || count > 5 { + return errors.Errorf("outside range [1, 5]: %v.", count) + } + default: + return errors.Errorf("unhandled period type: %v", period) + } + + return nil +} + +// Verifies the LACP timeout pings are working as expected. Pings can be sent either once every +// second (i.e. FAST), or once every 30 seconds (i.e. SLOW). This test allows for some variability +// in the exact number of pings sent and received, but will fail if the number isn't roughly what we +// expect based on the period type. +func verifyLACPTimeout(t *testing.T, hostActivity oc.E_Lacp_LacpActivityType, hostPeriod oc.E_Lacp_LacpPeriodType, peerActivity oc.E_Lacp_LacpActivityType, peerPeriod oc.E_Lacp_LacpPeriodType) error { + host := ondatra.DUT(t, "DUT") + peer := ondatra.DUT(t, "CONTROL") + t.Logf("Host Device: %v", host.Name()) + t.Logf("Peer Device: %v", peer.Name()) + + // Find a set of peer ports between the 2 switches. Notice this test uses multiple port to ensure + // the correct number of LACPDU packets are being sent per member. + peerPorts, err := testhelper.PeerPortGroupWithNumMembers(t, host, peer, 4) + if err != nil { + return err + } + t.Logf("Using peer ports: %v", peerPorts) + + // The interface config for PortChannels will be the same on both switches. + portChannel := "PortChannel200" + portChannelConfig := testhelper.GeneratePortChannelInterface(portChannel) + portChannelConfigs := map[string]*oc.Interface{portChannel: &portChannelConfig} + + // Configure the host side LACP settings. + hostLACPConfig := testhelper.GenerateLACPInterface(portChannel) + hostLACPConfig.LacpMode = hostActivity + hostLACPConfig.Interval = hostPeriod + var hostLACPConfigs oc.Lacp + hostLACPConfigs.AppendInterface(&hostLACPConfig) + hostDeviceConfig := &oc.Root{ + Interface: portChannelConfigs, + Lacp: &hostLACPConfigs, + } + gnmi.Replace(t, host, gnmi.OC().Config(), hostDeviceConfig) + defer func() { + if err := testhelper.RemovePortChannelFromDevice(t, defaultGNMIWait, host, portChannel); err != nil { + t.Fatalf("Failed to remove %v:%v: %v", host.Name(), portChannel, err) + } + }() + + // Configure the peer side LACP settings. + peerLACPConfig := testhelper.GenerateLACPInterface(portChannel) + peerLACPConfig.LacpMode = peerActivity + peerLACPConfig.Interval = peerPeriod + var peerLACPConfigs oc.Lacp + peerLACPConfigs.AppendInterface(&peerLACPConfig) + peerDeviceConfig := &oc.Root{ + Interface: portChannelConfigs, + Lacp: &peerLACPConfigs, + } + gnmi.Replace(t, peer, gnmi.OC().Config(), peerDeviceConfig) + defer func() { + if err := testhelper.RemovePortChannelFromDevice(t, defaultGNMIWait, peer, portChannel); err != nil { + t.Fatalf("Failed to remove %v:%v: %v", peer.Name(), portChannel, err) + } + }() + + // Assign all ethernet ports to the port channels on each switch so we will be getting multiple + // LACPDU packets in flight. + testhelper.AssignPortsToAggregateID(t, host, portChannel, peerPorts[0].Host, peerPorts[1].Host, peerPorts[2].Host, peerPorts[3].Host) + testhelper.AssignPortsToAggregateID(t, peer, portChannel, peerPorts[0].Peer, peerPorts[1].Peer, peerPorts[2].Peer, peerPorts[3].Peer) + + // Wait for the PortChannel to become active on each device. Then because LACPDU packets are used + // to notify peers about any state changes we sleep for a few seconds to give things time to + // converge. + gnmi.Await(t, host, gnmi.OC().Interface(portChannel).Enabled().State(), defaultGNMIWait, true) + gnmi.Await(t, peer, gnmi.OC().Interface(portChannel).Enabled().State(), defaultGNMIWait, true) + time.Sleep(3 * time.Second) + + // Choose a random port to test, and get the LACPDU count. + peerportslen := len(peerPorts) + max := big.NewInt(peerportslen) + randomIndex, _ := rand.Int(rand.Reader, max) + port := randomIndex + hostBefore := gnmi.Get(t, host, gnmi.OC().Lacp().Interface(portChannel).Member(peerPorts[port].Host).Counters().State()) + peerBefore := gnmi.Get(t, peer, gnmi.OC().Lacp().Interface(portChannel).Member(peerPorts[port].Peer).Counters().State()) + + // Then sleep for a minute and get the count again. + time.Sleep(time.Minute) + hostAfter := gnmi.Get(t, host, gnmi.OC().Lacp().Interface(portChannel).Member(peerPorts[port].Host).Counters().State()) + peerAfter := gnmi.Get(t, peer, gnmi.OC().Lacp().Interface(portChannel).Member(peerPorts[port].Peer).Counters().State()) + + // Finally, verify that the total number of LACPDU packets is acceptable for that 1 minute range. + hostCount := hostAfter.GetLacpInPkts() - hostBefore.GetLacpInPkts() + if err := acceptableLACPDUPacketCountForOneMinute(hostPeriod, hostCount); err != nil { + t.Errorf("Host LACPDU count is unacceptable for %v:%v: %v", host.Name(), peerPorts[port].Host, err) + } + peerCount := peerAfter.GetLacpInPkts() - peerBefore.GetLacpInPkts() + if err := acceptableLACPDUPacketCountForOneMinute(peerPeriod, peerCount); err != nil { + t.Errorf("Peer LACPDU count is unacceptable for %v:%v: %v", peer.Name(), peerPorts[port].Peer, err) + } + + // Also do a sanity check that gNMI is reporting the LacpOutPkts. Assuming we get here without + // failure then we know LACP is sending the packets out so we don't really care what this value is + // so long at it's >0. + if outPackets := hostAfter.GetLacpOutPkts(); outPackets == 0 { + t.Errorf("Host is not reporting any LACPDU output packets: got=%v", outPackets) + } + + return nil +} + +func TestLACPTimeouts(t *testing.T) { + // Testing LACPDU behavior with different timeout & activity settings (b4feaa45) and + // LACPDU counters (e9805bdf). + defer testhelper.NewTearDownOptions(t).WithID("b4feaa45-6088-4fa5-9f62-8adbc933c693").WithID("e9805bdf-1349-4fec-940b-1c710dc0c849").Teardown(t) + + tests := []struct { + hostActivity oc.E_Lacp_LacpActivityType + hostPeriod oc.E_Lacp_LacpPeriodType + peerActivity oc.E_Lacp_LacpActivityType + peerPeriod oc.E_Lacp_LacpPeriodType + }{ + {oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_FAST, oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_FAST}, + {oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_FAST, oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_SLOW}, + {oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_FAST, oc.Lacp_LacpActivityType_PASSIVE, oc.Lacp_LacpPeriodType_FAST}, + {oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_FAST, oc.Lacp_LacpActivityType_PASSIVE, oc.Lacp_LacpPeriodType_SLOW}, + {oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_SLOW, oc.Lacp_LacpActivityType_PASSIVE, oc.Lacp_LacpPeriodType_FAST}, + {oc.Lacp_LacpActivityType_ACTIVE, oc.Lacp_LacpPeriodType_SLOW, oc.Lacp_LacpActivityType_PASSIVE, oc.Lacp_LacpPeriodType_SLOW}, + } + + for _, test := range tests { + // Pretty print the test name based on the activity & period settings for host and peer switches. + // The names should look like: ActiveFastWithPassiveSlow, ActiveFastWithActiveSlow, etc. + hostSettings := lacpActivityTypeToString(test.hostActivity) + lacpPeriodTypeToString(test.hostPeriod) + peerSettings := lacpActivityTypeToString(test.peerActivity) + lacpPeriodTypeToString(test.peerPeriod) + name := hostSettings + "With" + peerSettings + + t.Run(name, func(t *testing.T) { + if got := verifyLACPTimeout(t, test.hostActivity, test.hostPeriod, test.peerActivity, test.peerPeriod); got != nil { + t.Errorf("LACP timeout test failed: %v", got) + } + }) + } +} + +// Used by go/ondatra to automatically reserve an available testbed. +func TestMain(m *testing.M) { + ondatra.RunTests(m, pinsbind.New) +} diff --git a/sdn_tests/pins_ondatra/tests/link_event_damping_test.go b/sdn_tests/pins_ondatra/tests/link_event_damping_test.go new file mode 100644 index 00000000000..e5d8db775ea --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/link_event_damping_test.go @@ -0,0 +1,436 @@ +package link_event_damping_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/openconfigondatra" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/binding/pinsbind" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper" + "github.com/pkg/errors" + "google.golang.org/grpc" + + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + holdTimeDisableMs = uint32(0) + holdTimeEnableMs = uint32(1000) + loopbackModeEnabled = oc.Interfaces_LoopbackModeType_FACILITY + waitAFterLinkEventDampingDisable = 1 * time.Second // Wait time after disabling damping config to ensure damped event got advertised and updated in DB. + waitAfterLoopbackModeChange = 10 * time.Second // Maximum time for link to flap and get updated in state DB after loopback-mode change. + configTimeout = 5 * time.Second // Maximum allowed time for a config to get programmed. + upTransitionNotificationTimeout = 3 * time.Second // Maximum allowed time for test to receive link up event notification on admin enable operation. + downTransitionNotificationTimeout = 3 * time.Second // Maximum allowed time for test to receive link down event notification on admin disable operation. +) + +func setLinkEventDampingConfig(t *testing.T, dut *ondatra.DUTDevice, intf string, holdTimeUp uint32) { + t.Helper() + // Configure the link event damping config. + gnmi.Replace(t, dut, gnmi.OC().Interface(intf).HoldTime().Up().Config(), holdTimeUp) + gnmi.Await(t, dut, gnmi.OC().Interface(intf).HoldTime().Up().State(), configTimeout, holdTimeUp) +} + +func fetchLinkEventDampingConfig(t *testing.T, dut *ondatra.DUTDevice, intf string) uint32 { + t.Helper() + // Lookup the hold time UP state value. + holdTimeUp, present := gnmi.Lookup(t, dut, gnmi.OC().Interface(intf).HoldTime().Up().State()).Val() + if present { + return holdTimeUp + } + return 0 +} + +func setLoopbackMode(t *testing.T, dut *ondatra.DUTDevice, intf string, loopbackMode oc.E_Interfaces_LoopbackModeType) { + t.Helper() + // Configure the loopback-mode. + gnmi.Replace(t, dut, gnmi.OC().Interface(intf).LoopbackMode().Config(), loopbackMode) + gnmi.Await(t, dut.GNMIOpts().WithYGNMIOpts(ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_ON_CHANGE)), gnmi.OC().Interface(intf).LoopbackMode().State(), configTimeout, loopbackMode) +} + +func fetchLoopbackMode(t *testing.T, dut *ondatra.DUTDevice, intf string) oc.E_Interfaces_LoopbackModeType { + t.Helper() + // Lookup the loopback mode state value. + loopbackMode, present := gnmi.Lookup(t, dut, gnmi.OC().Interface(intf).LoopbackMode().State()).Val() + if !present { + return oc.Interfaces_LoopbackModeType_NONE + } + return loopbackMode +} + +func restoreConfig(t *testing.T, dut *ondatra.DUTDevice, control *ondatra.DUTDevice, intf string, controlIntf string, loopbackModeDut oc.E_Interfaces_LoopbackModeType, + holdTimeUpDut uint32, holdTimeUpControl uint32) { + t.Helper() + + setLoopbackMode(t, dut, intf, loopbackModeDut) + time.Sleep(waitAfterLoopbackModeChange) + + var dutIntfNotUp bool = false + var controlIntfNotUp bool = false + // Verify port is UP after test. + if operStatus := gnmi.Get(t, dut, gnmi.OC().Interface(intf).OperStatus().State()); operStatus != oc.Interface_OperStatus_UP { + t.Errorf("DUT: got %v but want %v.", operStatus, oc.Interface_OperStatus_UP) + dutIntfNotUp = true + } + if operStatus := gnmi.Get(t, control, gnmi.OC().Interface(controlIntf).OperStatus().State()); operStatus != oc.Interface_OperStatus_UP { + t.Errorf("Control switch: got %v but want %v.", operStatus, oc.Interface_OperStatus_UP) + controlIntfNotUp = true + } + + // If port is not UP, try to bring it UP. + if dutIntfNotUp || controlIntfNotUp { + if dutIntfNotUp { + gnmi.Replace(t, dut, gnmi.OC().Interface(intf).Enabled().Config(), true) + } + if controlIntfNotUp { + gnmi.Replace(t, control, gnmi.OC().Interface(controlIntf).Enabled().Config(), true) + } + maxTimeForPortToComeUp := 15 * time.Second + if err := testhelper.WaitForInterfaceState(t, dut, intf, oc.Interface_OperStatus_UP, maxTimeForPortToComeUp); err != nil { + t.Errorf("DUT: failed to bring port UP: %v", err) + } + if err := testhelper.WaitForInterfaceState(t, dut, intf, oc.Interface_OperStatus_UP, maxTimeForPortToComeUp); err != nil { + t.Errorf("Control switch: failed to bring port UP: %v", err) + } + } + + setLinkEventDampingConfig(t, dut, intf, holdTimeUpDut) + setLinkEventDampingConfig(t, control, controlIntf, holdTimeUpControl) +} + +// Flaps a port N times and collects the port oper status change notifications. +func flapPortAndCollectOperStatusNotifications(t *testing.T, dut *ondatra.DUTDevice, intf string, numberOfFlaps int, + verifyStateAfterOp bool, timeout time.Duration) ([]*ygnmi.Value[oc.E_Interface_OperStatus], error) { + t.Helper() + + var operStatusSamples []*ygnmi.Value[oc.E_Interface_OperStatus] + var failed bool = false + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + gnmiClient, err := dut.RawAPIs().BindingDUT().DialGNMI(context.Background(), grpc.WithBlock()) + if err != nil { + t.Fatalf("Unable to get gNMI client (%v)", err) + } + operStatusSamples = gnmi.Collect(t, dut.GNMIOpts().WithClient(gnmiClient). + WithYGNMIOpts(ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_ON_CHANGE)), gnmi.OC().Interface(intf). + OperStatus().State(), timeout).Await(t) + // One extra sample is always received during collection - sample received + // immediately after subscription, so remove that sample from the list. + if len(operStatusSamples) >= 1 { + operStatusSamples = operStatusSamples[1:] + } + t.Logf("Successfully got ON_CHANGE oper status sample: %v", operStatusSamples) + }() + + // Wait for ON_CHANGE collect request to be sent before flapping the port. + time.Sleep(2 * time.Second) + for i := 0; i < numberOfFlaps; i++ { + t.Logf("Flap count: %v", i+1) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf).Enabled().Config(), false) + if verifyStateAfterOp { + if err := testhelper.WaitForInterfaceState(t, dut, intf, oc.Interface_OperStatus_DOWN, downTransitionNotificationTimeout); err != nil { + t.Errorf("%v", err) + failed = true + } + } else { + time.Sleep(downTransitionNotificationTimeout) + } + gnmi.Replace(t, dut, gnmi.OC().Interface(intf).Enabled().Config(), true) + if verifyStateAfterOp { + if err := testhelper.WaitForInterfaceState(t, dut, intf, oc.Interface_OperStatus_UP, upTransitionNotificationTimeout); err != nil { + t.Errorf("%v", err) + failed = true + } + } else { + time.Sleep(upTransitionNotificationTimeout) + } + } + + // Wait for oper status sample collection go routine to complete before + // verifying the sample result. + wg.Wait() + + if failed == true { + return nil, errors.Errorf("Verify state failed after port operation.") + } + return operStatusSamples, nil +} + +func isLinkUp(t *testing.T, dut *ondatra.DUTDevice, control *ondatra.DUTDevice, intf string, controlIntf string) error { + t.Helper() + + // Check port is UP. + if operStatus := gnmi.Get(t, dut, gnmi.OC().Interface(intf).OperStatus().State()); operStatus != oc.Interface_OperStatus_UP { + return errors.Errorf("DUT: got %v oper status but want %v.", operStatus, oc.Interface_OperStatus_UP) + } + if operStatus := gnmi.Get(t, control, gnmi.OC().Interface(controlIntf).OperStatus().State()); operStatus != oc.Interface_OperStatus_UP { + return errors.Errorf("control switch: got %v oper status but want %v.", operStatus, oc.Interface_OperStatus_UP) + } + return nil +} + +// Returns a port to run the test. +func selectPortToRunTest(t *testing.T, dut *ondatra.DUTDevice, control *ondatra.DUTDevice) (string, string, error) { + t.Helper() + + var portList []string + for _, port := range dut.Ports() { + portList = append(portList, port.Name()) + } + + dutIntf, err := testhelper.RandomInterface(t, dut, &testhelper.RandomInterfaceParams{PortList: portList}) + if err != nil { + return "", "", errors.Errorf("RandomInterface failed to get an UP interface on DUT: %v", err) + } + for _, port := range dut.Ports() { + if port.Name() == dutIntf { + if control.Port(t, port.ID()) == nil { + return "", "", errors.Errorf("control interface for DUT interface %v not found", dutIntf) + } + return dutIntf, control.Port(t, port.ID()).Name(), nil + } + } + return "", "", errors.Errorf("control interface for DUT interface %v not found", dutIntf) +} + +func TestMain(m *testing.M) { + ondatra.RunTests(m, pinsbind.New) +} + +// Disables the link event damping config on a link and does N flaps on the +// interface and verifies that link events are not damped. +func TestLinkEventDampingConfigDisabled(t *testing.T) { + // Report results to TestTracker at the end. + defer testhelper.NewTearDownOptions(t).WithID("396d27cb-f951-4acf-8cec-98b17a9a5175").Teardown(t) + + dut := ondatra.DUT(t, "DUT") + control := ondatra.DUT(t, "CONTROL") + // Select a random UP interface. + intf, controlIntf, err := selectPortToRunTest(t, dut, control) + if err != nil { + t.Fatalf("selectPortToRunTest() failed to get a port to run test: %v", err) + } + t.Logf("Running test on DUT interface: %v, control interface: %v.", intf, controlIntf) + + err = isLinkUp(t, dut, control, intf, controlIntf) + if err != nil { + t.Fatalf("isLinkUp() failed: %v", err) + } + + // Save the current config on port. + oldHoldTimeUpDut := fetchLinkEventDampingConfig(t, dut, intf) + t.Logf("Initial hold-time up config on DUT interface: %v", oldHoldTimeUpDut) + oldLoopbackModeDut := fetchLoopbackMode(t, dut, intf) + t.Logf("Initial loopback-mode on DUT interface: %v", oldLoopbackModeDut) + oldHoldTimeUpControl := fetchLinkEventDampingConfig(t, control, controlIntf) + t.Logf("Initial hold-time up config on control interface: %v", oldHoldTimeUpControl) + + // Restore the config after the test. + t.Cleanup(func() { + restoreConfig(t, dut, control, intf, controlIntf, oldLoopbackModeDut, oldHoldTimeUpDut, oldHoldTimeUpControl) + }) + + // Disable link event damping. + setLinkEventDampingConfig(t, dut, intf, holdTimeDisableMs) + setLinkEventDampingConfig(t, control, controlIntf, holdTimeDisableMs) + time.Sleep(waitAFterLinkEventDampingDisable) + // Set MAC loopback on port. + setLoopbackMode(t, dut, intf, loopbackModeEnabled) + time.Sleep(waitAfterLoopbackModeChange) + // Get initial carrier transitions. + initCarrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()) + t.Logf("Initial carrier transitions: %v", initCarrierTransitions) + + numberOfFlaps := 10 + // Flap the port and collect oper status samples. + collectTimeout := time.Duration(numberOfFlaps)*(upTransitionNotificationTimeout+downTransitionNotificationTimeout) + 5*time.Second + operStatusSamples, flapError := flapPortAndCollectOperStatusNotifications(t, dut, intf, numberOfFlaps, true, collectTimeout) + if flapError != nil { + t.Errorf("flapPortAndCollectOperStatusNotifications failed on port: %v", flapError) + } else if len(operStatusSamples) != 2*numberOfFlaps { + t.Errorf("flapPortAndCollectOperStatusNotifications got %v samples, want %v samples.", len(operStatusSamples), 2*numberOfFlaps) + } + finalCarrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()) + t.Logf("Final carrier transitions: %v", finalCarrierTransitions) + + // Verify the events count. + carrierTransitions := finalCarrierTransitions - initCarrierTransitions + if carrierTransitions != uint64(2*numberOfFlaps) { + t.Errorf("Got: %v carrier transitions but want: %v", carrierTransitions, 2*numberOfFlaps) + } +} + +// Tests that first link flap events on an interface is not damped after +// enabling the link event damping config. +func TestFirstFlapEventsNotDampedAfterLinkEventDampingConfig(t *testing.T) { + // Report results to TestTracker at the end. + defer testhelper.NewTearDownOptions(t).WithID("a92fd0c4-0461-4379-a8ba-76ce6710d8a4").Teardown(t) + + dut := ondatra.DUT(t, "DUT") + control := ondatra.DUT(t, "CONTROL") + intf, controlIntf, err := selectPortToRunTest(t, dut, control) + if err != nil { + t.Fatalf("selectPortToRunTest() failed to get a port to run test: %v", err) + } + t.Logf("Running test on DUT interface: %v, control interface: %v.", intf, controlIntf) + + err = isLinkUp(t, dut, control, intf, controlIntf) + if err != nil { + t.Fatalf("isLinkUp() failed: %v", err) + } + + // Save the current config on port. + oldHoldTimeUpDut := fetchLinkEventDampingConfig(t, dut, intf) + t.Logf("Initial hold-time up config on DUT interface: %v", oldHoldTimeUpDut) + oldLoopbackModeDut := fetchLoopbackMode(t, dut, intf) + t.Logf("Initial loopback-mode on DUT interface: %v", oldLoopbackModeDut) + oldHoldTimeUpControl := fetchLinkEventDampingConfig(t, control, controlIntf) + t.Logf("Initial hold-time up config on control interface: %v", oldHoldTimeUpControl) + + // Restore the config after the test. + t.Cleanup(func() { + // Disable link event damping config on port to clear the damped state + // before restoring the config. + setLinkEventDampingConfig(t, dut, intf, holdTimeDisableMs) + time.Sleep(waitAFterLinkEventDampingDisable) + restoreConfig(t, dut, control, intf, controlIntf, oldLoopbackModeDut, oldHoldTimeUpDut, oldHoldTimeUpControl) + }) + + // Disable link event damping so that damped state is cleared if present. + setLinkEventDampingConfig(t, dut, intf, holdTimeDisableMs) + setLinkEventDampingConfig(t, control, controlIntf, holdTimeDisableMs) + time.Sleep(waitAFterLinkEventDampingDisable) + // Set MAC loopback on port. + setLoopbackMode(t, dut, intf, loopbackModeEnabled) + time.Sleep(waitAfterLoopbackModeChange) + // Enable link event damping on DUT. + setLinkEventDampingConfig(t, dut, intf, holdTimeEnableMs) + // Get initial carrier transitions. + initCarrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()) + t.Logf("Initial carrier transitions: %v", initCarrierTransitions) + + // Flap the port and events should not be damped and notified immediately. + numberOfFlaps := 1 + collectTimeout := time.Duration(numberOfFlaps)*(upTransitionNotificationTimeout+downTransitionNotificationTimeout) + 5*time.Second + operStatusSamples, flapError := flapPortAndCollectOperStatusNotifications(t, dut, intf, numberOfFlaps, true, collectTimeout) + if flapError != nil { + t.Errorf("flapPortAndCollectOperStatusNotifications failed on port: %v", flapError) + } else if len(operStatusSamples) != 2 { + t.Errorf("flapPortAndCollectOperStatusNotifications got %v samples, want 2 samples.", len(operStatusSamples)) + } + if carrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()); carrierTransitions != initCarrierTransitions+2 { + t.Errorf("Got: %v carrier transitions but want: %v", carrierTransitions, initCarrierTransitions+2) + } +} + +// Tests that when multiple flaps happen one after another on a port, port +// remains damped. +func TestMultipleFlapsWithLinkEventDampingConfig(t *testing.T) { + // Report results to TestTracker at the end. + defer testhelper.NewTearDownOptions(t).WithID("5ccd7b33-8661-4b82-9d19-971816b2aeea").Teardown(t) + + dut := ondatra.DUT(t, "DUT") + control := ondatra.DUT(t, "CONTROL") + intf, controlIntf, err := selectPortToRunTest(t, dut, control) + if err != nil { + t.Fatalf("selectPortToRunTest() failed to get a port to run test: %v", err) + } + t.Logf("Running test on DUT interface: %v, control interface: %v.", intf, controlIntf) + + err = isLinkUp(t, dut, control, intf, controlIntf) + if err != nil { + t.Fatalf("isLinkUp() failed: %v", err) + } + + // Save the current config on port. + oldHoldTimeUpDut := fetchLinkEventDampingConfig(t, dut, intf) + t.Logf("Initial hold-time up config on DUT interface: %v", oldHoldTimeUpDut) + oldLoopbackModeDut := fetchLoopbackMode(t, dut, intf) + t.Logf("Initial loopback-mode on DUT interface: %v", oldLoopbackModeDut) + oldHoldTimeUpControl := fetchLinkEventDampingConfig(t, control, controlIntf) + t.Logf("Initial hold-time up config on control interface: %v", oldHoldTimeUpControl) + + // Restore the config after the test. + t.Cleanup(func() { + // Disable link event damping config on port to clear the damped state + // before restoring the config. + setLinkEventDampingConfig(t, dut, intf, holdTimeDisableMs) + time.Sleep(waitAFterLinkEventDampingDisable) + restoreConfig(t, dut, control, intf, controlIntf, oldLoopbackModeDut, oldHoldTimeUpDut, oldHoldTimeUpControl) + }) + + // Disable link event damping so that damped state is cleared if present. + setLinkEventDampingConfig(t, dut, intf, holdTimeDisableMs) + setLinkEventDampingConfig(t, control, controlIntf, holdTimeDisableMs) + time.Sleep(waitAFterLinkEventDampingDisable) + // Set MAC loopback on port. + setLoopbackMode(t, dut, intf, loopbackModeEnabled) + time.Sleep(waitAfterLoopbackModeChange) + // Enable link event damping. + setLinkEventDampingConfig(t, dut, intf, holdTimeEnableMs) + // Get initial carrier transitions. + initCarrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()) + t.Logf("Initial carrier transitions: %v", initCarrierTransitions) + + // First flap events should not be damped after config enable and notified + // immediately. + numberOfFlaps := 1 + collectTimeout := time.Duration(numberOfFlaps)*(upTransitionNotificationTimeout+downTransitionNotificationTimeout) + 5*time.Second + operStatusSamples, flapError := flapPortAndCollectOperStatusNotifications(t, dut, intf, numberOfFlaps, true, collectTimeout) + if flapError != nil { + t.Errorf("flapPortAndCollectOperStatusNotifications failed to verify first flap on port: %v", flapError) + } else if len(operStatusSamples) != 2 { + t.Errorf("flapPortAndCollectOperStatusNotifications got %v samples, but want 2 samples.", len(operStatusSamples)) + } + if carrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()); carrierTransitions != initCarrierTransitions+2 { + t.Errorf("Got: %v carrier transitions but want: %v", carrierTransitions, initCarrierTransitions+2) + } + // DOWN event of second flap should start the damping and DOWN event + // notification should be observed but UP event will be damped. + operStatusSamples, flapError = flapPortAndCollectOperStatusNotifications(t, dut, intf, numberOfFlaps, false, collectTimeout) + if flapError != nil { + t.Errorf("flapPortAndCollectOperStatusNotifications failed on port: %v", flapError) + } else if len(operStatusSamples) != 1 { + t.Errorf("flapPortAndCollectOperStatusNotifications got %v samples, want 1 sample.", len(operStatusSamples)) + } + if carrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()); carrierTransitions != initCarrierTransitions+3 { + t.Errorf("Got: %v carrier transitions but want: %v", carrierTransitions, initCarrierTransitions+3) + } + if operStatus := gnmi.Get(t, dut, gnmi.OC().Interface(intf).OperStatus().State()); operStatus != oc.Interface_OperStatus_DOWN { + t.Errorf("Got %v oper status but want %v.", operStatus, oc.Interface_OperStatus_DOWN) + } + // Subsequent flap events should be damped. + numberOfFlaps = 10 + collectTimeout = time.Duration(numberOfFlaps)*(upTransitionNotificationTimeout+downTransitionNotificationTimeout) + 30*time.Second + operStatusSamples, flapError = flapPortAndCollectOperStatusNotifications(t, dut, intf, numberOfFlaps, false, collectTimeout) + if flapError != nil { + t.Errorf("flapPortAndCollectOperStatusNotifications failed on port: %v", flapError) + } else if len(operStatusSamples) != 0 { + t.Errorf("flapPortAndCollectOperStatusNotifications got %v samples, want 0 sample.", len(operStatusSamples)) + } + if carrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()); carrierTransitions != initCarrierTransitions+3 { + t.Errorf("Got: %v carrier transitions but want: %v", carrierTransitions, initCarrierTransitions+3) + } + if operStatus := gnmi.Get(t, dut, gnmi.OC().Interface(intf).OperStatus().State()); operStatus != oc.Interface_OperStatus_DOWN { + t.Errorf("Got %v oper status but want %v.", operStatus, oc.Interface_OperStatus_DOWN) + } + // Disable link event damping so that damped state is cleared and UP + // notification is received. + setLinkEventDampingConfig(t, dut, intf, holdTimeDisableMs) + time.Sleep(waitAFterLinkEventDampingDisable) + + if carrierTransitions := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Counters().CarrierTransitions().State()); carrierTransitions != initCarrierTransitions+4 { + t.Errorf("Got: %v carrier transitions but want: %v", carrierTransitions, initCarrierTransitions+4) + } + if operStatus := gnmi.Get(t, dut, gnmi.OC().Interface(intf).OperStatus().State()); operStatus != oc.Interface_OperStatus_UP { + t.Errorf("Got %v oper status but want %v.", operStatus, oc.Interface_OperStatus_UP) + } +} diff --git a/sdn_tests/pins_ondatra/tests/module_reset_test.go b/sdn_tests/pins_ondatra/tests/module_reset_test.go new file mode 100644 index 00000000000..0345e1a62e8 --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/module_reset_test.go @@ -0,0 +1,213 @@ +package module_reset_test + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/binding/pinsbind" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper" + + syspb "github.com/openconfig/gnoi/system" + typespb "github.com/openconfig/gnoi/types" +) + +func TestMain(m *testing.M) { + ondatra.RunTests(m, pinsbind.New) +} + +const ( + waitTimeInterfacesUp = 60 * time.Second + transceiverPrefix = "Ethernet" +) + +// Enum to represent which modules to reset in each test case. +type testModules int + +const ( + oneModule testModules = iota + allModules +) + +func TestResetModules(t *testing.T) { + dut := ondatra.DUT(t, "DUT") + + // Get all ports connected to peer device. + var dutPorts []string + for _, port := range dut.Ports() { + dutPorts = append(dutPorts, port.Name()) + } + + tests := []struct { + name string + uuid string + modules testModules + }{ + { + name: "TestResetOneModule", + uuid: "4593ff89-892b-46f7-a049-959c8681912d", + modules: oneModule, + }, + { + name: "TestResetAllModules", + uuid: "af9877f2-40f3-4abe-873b-e1db155a917d", + modules: allModules, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer testhelper.NewTearDownOptions(t).WithID(tt.uuid).Teardown(t) + + operStatusInfo, err := testhelper.FetchPortsOperStatus(t, dut, dutPorts...) + if err != nil { + t.Fatalf("Failed to fetch ports oper status: %v", err) + } + upPorts := operStatusInfo.Up + if len(upPorts) == 0 { + t.Log("No up ports found at start of test") + } + + paths := []*typespb.Path{} + + // Name of transceiver to reset, for OneModule test. + var testXcvr string + + for _, component := range gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) { + xcvrName := component.GetName() + // Skip non-transceiver components. + if !strings.HasPrefix(xcvrName, transceiverPrefix) { + continue + } + if !component.GetEmpty() { + // Add transceiver name to paths. + pathElems := []*typespb.PathElem{ + &typespb.PathElem{Name: "components"}, + &typespb.PathElem{Name: "component", Key: map[string]string{"name": xcvrName}}, + } + path := &typespb.Path{ + Origin: "openconfig", + Elem: pathElems, + } + paths = append(paths, path) + if tt.modules == oneModule { + t.Logf("Testing transceiver %v", xcvrName) + testXcvr = xcvrName + break + } + } + } + if len(paths) == 0 { + t.Fatal("No non-empty transceivers found") + } + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Message: "Reset transceiver modules", + Subcomponents: paths, + } + + params := testhelper.NewRebootParams().WithWaitTime(0 * time.Second).WithCheckInterval(0 * time.Second).WithRequest(req) + + if err := testhelper.Reboot(t, dut, params); err != nil { + t.Fatalf("Reboot RPC failed: %v", err) + } + + // If test resets one module, verify that ports on all the other modules are still up. + if tt.modules == oneModule { + for _, intf := range upPorts { + // Verify that interfaces not on testXcvr are up. + xcvrName := gnmi.Get(t, dut, gnmi.OC().Interface(intf).Transceiver().State()) + if xcvrName != testXcvr { + if got := gnmi.Get(t, dut, gnmi.OC().Interface(intf).OperStatus().State()); got != oc.Interface_OperStatus_UP { + t.Errorf("Interface %v oper status is not UP", intf) + } + } + } + } + + if len(upPorts) > 0 { + time.Sleep(waitTimeInterfacesUp) + if err := testhelper.VerifyPortsOperStatus(t, dut, upPorts...); err != nil { + t.Fatalf("Not all ports are up at the end of the test %v: %v", tt.name, err) + } + } + }) + } +} + +func TestResetModuleInvalidTransceiver(t *testing.T) { + defer testhelper.NewTearDownOptions(t).WithID("9b1c5bb4-f79e-4aab-9488-ce7294728abd").Teardown(t) + dut := ondatra.DUT(t, "DUT") + + // Get all ports connected to peer device. + var dutPorts []string + for _, port := range dut.Ports() { + dutPorts = append(dutPorts, port.Name()) + } + + operStatusInfo, err := testhelper.FetchPortsOperStatus(t, dut, dutPorts...) + if err != nil { + t.Fatalf("Failed to fetch ports oper status: %v", err) + } + upPorts := operStatusInfo.Up + if len(upPorts) == 0 { + t.Log("No up ports found at start of test") + } + + maxXcvrNum := 0 + + for _, component := range gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) { + xcvrName := component.GetName() + // Skip non-transceiver components. + if !strings.HasPrefix(xcvrName, transceiverPrefix) { + continue + } + var phyPortNum int + _, err := fmt.Sscanf(xcvrName, transceiverPrefix+"%d", &phyPortNum) + if err == nil { + if phyPortNum > maxXcvrNum { + maxXcvrNum = phyPortNum + } + } + } + if maxXcvrNum == 0 { + t.Fatalf("No transceivers found") + } + + invalidXcvrName := fmt.Sprintf("Ethernet%v", maxXcvrNum+1) + t.Logf("Testing with invalid transceiver name %v", invalidXcvrName) + + pathElems := []*typespb.PathElem{ + &typespb.PathElem{Name: "components"}, + &typespb.PathElem{Name: "component", Key: map[string]string{"name": invalidXcvrName}}, + } + path := &typespb.Path{ + Origin: "openconfig", + Elem: pathElems, + } + paths := []*typespb.Path{path} + + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Message: "Reset transceiver", + Subcomponents: paths, + } + + params := testhelper.NewRebootParams().WithWaitTime(0 * time.Second).WithCheckInterval(0 * time.Second).WithRequest(req) + + err = testhelper.Reboot(t, dut, params) + t.Logf("Reboot err: %v", err) + + if err == nil { + t.Errorf("Reboot RPC expected to fail") + } + if len(upPorts) > 0 { + if err := testhelper.VerifyPortsOperStatus(t, dut, upPorts...); err != nil { + t.Fatalf("Not all ports are up at the end of the test: %v", err) + } + } +} diff --git a/sdn_tests/pins_ondatra/tests/ondatra_test.bzl b/sdn_tests/pins_ondatra/tests/ondatra_test.bzl index 55112e8cbaf..f4756f55ee8 100644 --- a/sdn_tests/pins_ondatra/tests/ondatra_test.bzl +++ b/sdn_tests/pins_ondatra/tests/ondatra_test.bzl @@ -27,8 +27,8 @@ def ondatra_test( data: List of labels; optional visibility: List of visibility labels; optional """ - data = (data or []) + ["//ondatra/data"] - testbed = testbed or "ondatra/data/testbeds.textproto" + data = (data or []) + ["//infrastructure/data"] + testbed = testbed or "infrastructure/data/testbeds.textproto" testbed_arg = "--testbed=%s" % testbed args = (args or []) + [ @@ -79,7 +79,7 @@ def ondatra_test_suite( visibility: List of visibility labels; optional """ if len(testbeds) == 0: - testbeds = {"dualnode" : "ondatra/data/testbeds.textproto"} + testbeds = {"dualnode" : "infrastructure/data/testbeds.textproto"} tests = [] for testbed_name, testbed_src in testbeds.items(): diff --git a/sdn_tests/pins_ondatra/tests/port_debug_data_test.go b/sdn_tests/pins_ondatra/tests/port_debug_data_test.go new file mode 100644 index 00000000000..554a81368c7 --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/port_debug_data_test.go @@ -0,0 +1,93 @@ +package port_debug_data_test + +import ( + "fmt" + "testing" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/binding/pinsbind" + "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper" +) + +func TestMain(m *testing.M) { + ondatra.RunTests(m, pinsbind.New) +} + +func TestGetPortDebugDataInvalidInterface(t *testing.T) { + defer testhelper.NewTearDownOptions(t).WithID("dba77fa7-b0d1-4412-8136-22dea24ed935").Teardown(t) + var intfName = "Ethernet99999" + if _, err := testhelper.HealthzGetPortDebugData(t, ondatra.DUT(t, "DUT"), intfName); err == nil { + t.Fatalf("Expected RPC failure due to invalid interface %v", intfName) + } +} + +func TestGetPortDebugDataWithTranscevierInserted(t *testing.T) { + defer testhelper.NewTearDownOptions(t).WithID("8f0468c5-6b2c-477c-9cb2-ec099a686268").Teardown(t) + dut := ondatra.DUT(t, "DUT") + + frontPanelPorts, err := testhelper.FrontPanelPortListForDevice(t, dut) + if err != nil { + t.Fatalf("Failed to fetch front panel ports with error %v", err) + } + + for _, intfName := range frontPanelPorts { + xcvrName := gnmi.Get(t, dut, gnmi.OC().Interface(intfName).Transceiver().State()) + if gnmi.Get(t, dut, gnmi.OC().Component(xcvrName).Empty().State()) { + // Skip the interfaces without transceiver inserted. + continue + } + + t.Logf("Get port debug data from interface %v on xcvr present port %v", intfName, xcvrName) + data, err := testhelper.HealthzGetPortDebugData(t, dut, intfName) + if err != nil { + t.Fatalf("Expected RPC success, got error %v", err) + } + + if data.GetPhyData() == "" { + t.Errorf("Got empty phy_data from PortDebugData for intfName %v", intfName) + } + + if len(data.GetTransceiverEepromPages()) == 0 { + t.Errorf("Got empty transceiver_eeprom_pages from PortDebugData for intfName %v", intfName) + } + + for _, eepromPage := range data.GetTransceiverEepromPages() { + if len(eepromPage.GetEepromContent()) == 0 { + t.Errorf("Got empty eeprom_content on page %v from PortDebugData for intfName %v", eepromPage.GetPageNum(), intfName) + } + } + } +} + +func TestGetPortDebugDataWithoutTranscevierInserted(t *testing.T) { + defer testhelper.NewTearDownOptions(t).WithID("2229d2e5-1e0b-415b-ac23-b5b05f76e6d4").Teardown(t) + dut := ondatra.DUT(t, "DUT") + frontPanelPorts, err := testhelper.FrontPanelPortListForDevice(t, dut) + if err != nil { + t.Fatalf("Failed to fetch front panel ports") + } + + for _, intfName := range frontPanelPorts { + xcvrName := gnmi.Get(t, dut, gnmi.OC().Interface(intfName).Transceiver().State()) + if !gnmi.Get(t, dut, gnmi.OC().Component(xcvrName).Empty().State()) { + // Skip the interfaces with transceiver inserted. + fmt.Println(intfName + " : " + xcvrName) + continue + } + + t.Logf("Get port debug data from interface %v on xcvr empty port %v", intfName, xcvrName) + data, err := testhelper.HealthzGetPortDebugData(t, dut, intfName) + if err != nil { + t.Fatalf("Expected RPC success, got error %v", err) + } + + if data.GetPhyData() == "" { + t.Errorf("Got empty phy_data from PortDebugData for intfName %v", intfName) + } + + if len(data.GetTransceiverEepromPages()) != 0 { + t.Errorf("Got non-empty transceiver_eeprom_pages from PortDebugData for intfName %v", intfName) + } + } +}