diff --git a/sdn_tests/pins_ondatra/bazel/patches/ondatra.patch b/sdn_tests/pins_ondatra/bazel/patches/ondatra.patch index 300836ae009..74acd035afd 100644 --- a/sdn_tests/pins_ondatra/bazel/patches/ondatra.patch +++ b/sdn_tests/pins_ondatra/bazel/patches/ondatra.patch @@ -7,6 +7,19 @@ index 4d431d1..0ff43a4 100644 pathzpb "github.com/openconfig/gnsi/pathz" - grpb "github.com/openconfig/gribi/v1/proto/service" ++ grpb "github.com/openconfig/gribi/proto/service" + opb "github.com/openconfig/ondatra/proto" + p4pb "github.com/p4lang/p4runtime/go/p4/v1" + ) +diff --git a/fakebind/fakebind.go b/fakebind/fakebind.go +index 7dd0538..800a272 100644 +--- a/fakebind/fakebind.go ++++ b/fakebind/fakebind.go +@@ -28,7 +28,7 @@ import ( + "google.golang.org/grpc" + + gpb "github.com/openconfig/gnmi/proto/gnmi" +- grpb "github.com/openconfig/gribi/v1/proto/service" + grpb "github.com/openconfig/gribi/proto/service" opb "github.com/openconfig/ondatra/proto" p4pb "github.com/p4lang/p4runtime/go/p4/v1" @@ -141,4 +154,4 @@ index 7145250..85cb489 100644 +import "github.com/openconfig/ondatra/proto/testbed.proto"; option go_package = "github.com/openconfig/ondatra/proto/reservation"; - \ No newline at end of file + diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/BUILD.bazel b/sdn_tests/pins_ondatra/infrastructure/testhelper/BUILD.bazel index 4e2e3f5e65f..b35f18760a2 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/BUILD.bazel +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/BUILD.bazel @@ -1,5 +1,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") +def_visibility = [ + "//platforms/networking/gpins/testing:__subpackages__", + "//third_party/pins_infra:__subpackages__", +] + package( default_visibility = ["//visibility:public"], licenses = ["notice"], @@ -21,6 +26,7 @@ go_library( "platform_info.go", "port_management.go", "results.go", + "utils.go", "ssh.go", "//infrastructure/testhelper/platform_info:platform_info", ], diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/config_convergence.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/config_convergence.go index b67a00f59ac..63987bcb9a6 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/config_convergence.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/config_convergence.go @@ -174,26 +174,6 @@ func CompareConfigAndStateValues(ctx context.Context, t *testing.T, dut *ondatra return r.String(), nil } -// pollFunc returns true if the condition is met. -type pollFunc func() bool - -// poll polls the condition until it is met or the context is done. -func poll(ctx context.Context, t *testing.T, interval time.Duration, pf pollFunc) error { - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return fmt.Errorf("polling for condition failed, err: %v", ctx.Err()) - case <-ticker.C: - if pf() { - log.InfoContextf(ctx, "polling done") - return nil - } - } - } -} - // WaitForConfigConvergence checks for differences between config and state. // Polls till configConvergenceTimeout, // returns error if the difference still exists. @@ -208,18 +188,18 @@ func WaitForConfigConvergence(ctx context.Context, t *testing.T, dut *ondatra.DU defer cancel() dutName := dut.Name() // Poll until the config and state are similar. - return poll(ctx, t, configConvergencePollInterval, func() bool { + return poll(ctx, configConvergencePollInterval, func() pollStatus { diff, err := CompareConfigAndStateValues(ctx, t, dut, config) if err != nil { log.InfoContextf(ctx, "Comparing config and state failed for dut: %v, err: %v", dutName, err) - return false + return continuePoll } if diff == "" { log.InfoContextf(ctx, "Config and state converged for dut: %v", dutName) - return true + return exitPoll } log.InfoContextf(ctx, "diff in config and state found for dut: %v\ndiff_begin:\n%v\ndiff_end\n", dutName, diff) - return false + return continuePoll }) } @@ -369,30 +349,30 @@ func WaitForSwitchState(ctx context.Context, t *testing.T, dut *ondatra.DUTDevic defer cancel() // Poll until the switch is ready or the context is done. - err := poll(ctx, t, switchStatePollInterval, func() bool { + err := poll(ctx, switchStatePollInterval, func() pollStatus { switch s := switchState; s { case down: if err := GNOIAble(t, dut); err != nil { log.InfoContextf(ctx, "GNOIAble(dut=%v) failed, err: %v", dutName, err) - return false + return continuePoll } switchState++ case gnoiAble: if err := GNMIAble(t, dut); err != nil { log.InfoContextf(ctx, "GNMIAble(dut=%v) failed, err: %v", dutName, err) - return false + return continuePoll } switchState++ case gnmiAble: if err := WaitForAllPortsUp(ctx, t, dut); err != nil { log.InfoContextf(ctx, "WaitForAllPortsUp(dut=%v) failed, err: %v", dutName, err) - return false + return continuePoll } switchState++ case portsUp: - return true + return exitPoll } - return false + return continuePoll }) if err == nil { diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/config_restorer.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/config_restorer.go index 24ce79fccd3..0dea5a35067 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/config_restorer.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/config_restorer.go @@ -93,10 +93,16 @@ func NewConfigRestorerWithIgnorePaths(t *testing.T, ignorePaths []string) *Confi t.Fatalf("config_restorer creation failed, errors: %v", err) } - // Register a cleanup to restore the configs on test end. - t.Cleanup(func() { - cr.RestoreConfigsAndClose(t) - }) + // Cleanup is only registered if running in a go test. + // As the cleanup callback would not be called otherwise. + if testing.Testing() { // True if running in a go test. + // Register a cleanup to restore the configs on test end. + log.InfoContextf(ctx, "Registering config_restorer for cleanup tasks.") + t.Cleanup(func() { + cr.RestoreConfigsAndClose(t) + }) + } + return cr } @@ -238,12 +244,10 @@ func (cr *ConfigRestorer) restoreConfigOnDiff(ctx context.Context, t *testing.T, // restoreReservedDevices tries to restore the config of the reserved devices // if the config differs from the saved config. -func (cr *ConfigRestorer) restoreReservedDevices(t *testing.T) { - t.Helper() +func (cr *ConfigRestorer) restoreReservedDevices(t *testing.T) error { ctx := context.Background() if cr.savedConfigs == nil { - log.InfoContextf(ctx, "configRestorer.savedConfigs is not initialized.") - return + return fmt.Errorf("savedConfigs are not initialized") } wg := sync.WaitGroup{} @@ -268,16 +272,24 @@ func (cr *ConfigRestorer) restoreReservedDevices(t *testing.T) { // Collect all the errors and fail the test on error. if err := collectErrors(errCh); err != nil { - t.Fatalf("failed to restore config, errors: %v", err) + return fmt.Errorf("failed to restore config, errors: %v", err) } log.InfoContextf(ctx, "Config restored for all the reserved devices.") + return nil +} + +// RestoreConfigs restores the config of reserved devices.s +func (cr *ConfigRestorer) RestoreConfigs(t *testing.T) error { + return cr.restoreReservedDevices(t) } // RestoreConfigsAndClose restores the config of reserved devices // and closes the configRestorer object. func (cr *ConfigRestorer) RestoreConfigsAndClose(t *testing.T) { t.Helper() - cr.restoreReservedDevices(t) + if err := cr.RestoreConfigs(t); err != nil { + t.Fatalf("config_restorer failed, errors: %v", err) + } cr.savedConfigs = nil cr.ignorePaths = nil } diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/gnoi.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/gnoi.go index 60679e03ccc..449c6425c07 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/gnoi.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/gnoi.go @@ -44,18 +44,20 @@ var ( // RebootParams specify the reboot parameters used by the Reboot API. type RebootParams struct { - request any - waitTime time.Duration - checkInterval time.Duration - lmTTkrID string // latency measurement testtracker UUID - lmTitle string // latency measurement title + request any + waitTime time.Duration + checkInterval time.Duration + requestTimeout time.Duration + lmTTkrID string // latency measurement testtracker UUID + lmTitle string // latency measurement title } // NewRebootParams returns RebootParams structure with default values. func NewRebootParams() *RebootParams { return &RebootParams{ - waitTime: 4 * time.Minute, - checkInterval: 20 * time.Second, + waitTime: 4 * time.Minute, + checkInterval: 20 * time.Second, + requestTimeout: 2 * time.Minute, } } @@ -82,6 +84,14 @@ func (p *RebootParams) WithRequest(r any) *RebootParams { return p } +// WithRequestTimeout adds the timeout for the reboot request. +// The function will wait for the gNOI server to be down within this duration. +// Default value is 2 minutes. +func (p *RebootParams) WithRequestTimeout(timeout time.Duration) *RebootParams { + p.requestTimeout = timeout + return p +} + // WithLatencyMeasurement adds testtracker uuid and title for latency measurement. func (p *RebootParams) WithLatencyMeasurement(testTrackerID, title string) *RebootParams { p.lmTTkrID = testTrackerID @@ -132,28 +142,47 @@ func Reboot(t *testing.T, d *ondatra.DUTDevice, params *RebootParams) error { return nil } - log.Infof("Polling gNOI server reachability in %v intervals for max duration of %v", params.checkInterval, params.waitTime) + rebootRequestTimeout := params.requestTimeout + ctx, cancel := context.WithTimeout(context.Background(), rebootRequestTimeout) + defer cancel() + + // The switch backend might not have processed the request or might take + // sometime to execute the request. So poll for the gNOI server to be down, + // or context to expire. + pollErr := poll(ctx, 10*time.Second /*(pollInterval)*/, func() pollStatus { + err := GNOIAble(t, d) + timeElapsed := (time.Now().UnixNano() - timeBeforeReboot) / int64(time.Second) + if err == nil { + log.Infof("gNOI server is still up after %v seconds", timeElapsed) + return continuePoll + } + log.Infof("gNOI server is down after %v seconds, got error while checking gNOI server reachability: %v as expected", timeElapsed, err) + return exitPoll + }) + if pollErr != nil { + log.WarningContextf(ctx, "Polling gNOI server to be down within time: %v failed: %v", rebootRequestTimeout, pollErr) + log.InfoContextf(ctx, "Continue to check if the switch has rebooted") + } + + log.InfoContextf(ctx, "Polling gNOI server reachability in %v intervals for max duration of %v", params.checkInterval, params.waitTime) for timeout := time.Now().Add(params.waitTime); time.Now().Before(timeout); { - // The switch backend might not have processed the request or might take - // sometime to execute the request. So wait for check interval time and - // later verify that the switch rebooted within the specified wait time. time.Sleep(params.checkInterval) doneTime := time.Now() timeElapsed := (doneTime.UnixNano() - timeBeforeReboot) / int64(time.Second) if err := GNOIAble(t, d); err != nil { - log.Infof("gNOI server not up after %v seconds", timeElapsed) + log.InfoContextf(ctx, "gNOI server not up after %v seconds", timeElapsed) continue } - log.Infof("gNOI server up after %v seconds", timeElapsed) + log.InfoContextf(ctx, "gNOI server up after %v seconds", timeElapsed) // An extra check to ensure that the system has rebooted. if bootTime := gnmiSystemBootTimeGet(t, d); bootTime < uint64(timeBeforeReboot) { - log.Infof("Switch has not rebooted after %v seconds", timeElapsed) + log.InfoContextf(ctx, "Switch has not rebooted after %v seconds", timeElapsed) continue } - log.Infof("Switch rebooted after %v seconds", timeElapsed) + log.InfoContextf(ctx, "Switch rebooted after %v seconds", timeElapsed) return nil } diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/p4rt.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/p4rt.go index 1cb6c1120e0..4e918c3fd61 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/p4rt.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/p4rt.go @@ -15,6 +15,7 @@ import ( "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ygnmi/ygnmi" "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/protobuf/encoding/prototext" @@ -35,15 +36,29 @@ var ( return 0, errors.Errorf("failed to get port ID for port %v from switch", port) } testhelperDeviceIDGet = func(t *testing.T, d *ondatra.DUTDevice) (uint64, error) { - deviceInfo, present := gnmi.Lookup(t, d, gnmi.OC().Component(icName).IntegratedCircuit().State()).Val() + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + yc, err := ygnmiClient(ctx, d) + if err != nil { + return 0, fmt.Errorf("failed to create ygnmi client, err: %v", err) + } + v, err := ygnmi.Lookup(ctx, yc, gnmi.OC().Component(icName).IntegratedCircuit().State()) + if err != nil { + return 0, fmt.Errorf("failed to lookup device ID, err: %v", err) + } + deviceInfo, present := v.Val() if present && deviceInfo.NodeId != nil { return *deviceInfo.NodeId, nil } // Configure default device ID on the switch. - gnmi.Replace(t, d, gnmi.OC().Component(icName).IntegratedCircuit().NodeId().Config(), defaultDeviceID) + _, err = ygnmi.Replace(ctx, yc, gnmi.OC().Component(icName).IntegratedCircuit().NodeId().Config(), defaultDeviceID) + if err != nil { + return 0, fmt.Errorf("failed to configure default device ID, err: %v", err) + } // Verify that default device ID has been configured and return that. - if got, want := gnmi.Get(t, d, gnmi.OC().Component(icName).IntegratedCircuit().NodeId().State()), defaultDeviceID; got != want { - return 0, errors.Errorf("failed to configure default device ID") + devID, err := ygnmi.Await(ctx, yc, gnmi.OC().Component(icName).IntegratedCircuit().NodeId().State(), defaultDeviceID, nil) + if err != nil { + return 0, fmt.Errorf("waiting for device ID to be %v failed, have %v, err: %v", defaultDeviceID, devID.String(), err) } return defaultDeviceID, nil } diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/platform_components.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/platform_components.go index 224ed8e8411..e7bca9f90b5 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/platform_components.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/platform_components.go @@ -14,7 +14,7 @@ import ( // SwitchNameRegex returns the regex for switch name. func SwitchNameRegex() string { - return "" + return "^(ju|df|mn|ocs)(\\d+).*\\.([a-z]{3})(\\d{2})([a-z]?)\\.(net|prod).google.com$" } // ImageVersionRegex returns the regular expressions for the image version of the switch. diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/port_management.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/port_management.go index 8c8a514406f..8adf859b01a 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/port_management.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/port_management.go @@ -4,6 +4,8 @@ package testhelper import ( "fmt" + "math/rand" + "slices" "strconv" "strings" "testing" @@ -11,8 +13,11 @@ import ( "math" log "github.com/golang/glog" + gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" "github.com/pkg/errors" ) @@ -114,6 +119,34 @@ type RandomPortWithSupportedBreakoutModesParams struct { PortList []string // List of ports from which a random port can be selected } +// LinkDampingConfig contains the link damping config for a given interface. +type LinkDampingConfig struct { + // The maximum amount of time an interface can remain damped since the last + // link down event. A value of 0 disables link damping. A value of -1 + // indicates that penalty-based-aied is used instead. + HoldTime int32 + + // Maximum time an interface can remain damped since the last link down event + // (in milliseconds). + MaxSuppressTime uint32 + // The amount of time after which an interface’s penalty is decreased by half + // (in milliseconds). + DecayHalfLife uint32 + // The accumulated penalty that triggers the damping of an interface. A value + // of 0 disables link damping. + SuppressThreshold uint32 + // When the accumulated penalty decreases to this reuse threshold, the + // interface is not damped anymore. A value of 0 disables link damping. + ReuseThreshold uint32 + // A penalty that each down event costs. A value of 0 disables link damping. + FlapPenalty uint32 +} + +const ( + linkDampingPayload = "{\"config\": {\"max-suppress-time\": %d, \"decay-half-life\": %d, \"suppress-threshold\": %d, \"reuse-threshold\": %d, \"flap-penalty\": %d}}" + configTimeout = 5 * time.Second // Maximum allowed time for a config to get programmed. +) + // Uint16ListToString returns comma separate string representation of list of uint16. func Uint16ListToString(a []uint16) string { s := make([]string, len(a)) @@ -506,31 +539,40 @@ func interfaceConfigForPort(t *testing.T, d *ondatra.DUTDevice, intfName string, // Breakout mode is in the format "numBreakouts1 x breakoutSpeed1 + numBreakouts2 x breakoutSpeed2 + ... // Eg: "1x400G", 2x100G(4) + 1x200G(4)" func ConfigFromBreakoutMode(t *testing.T, dut *ondatra.DUTDevice, breakoutMode, port string) (*oc.Root, error) { + deviceConfig, _, err := ConfigFromBreakoutModeWithSkipLane(t, dut, breakoutMode, port, nil, false) + return deviceConfig, err +} + +// ConfigFromBreakoutModeWithSkipLane returns config with component and interface paths for given +// breakout mode. Interface paths allow missing for some ports. +// Breakout mode is in the format "numBreakouts1 x breakoutSpeed1 + numBreakouts2 x breakoutSpeed2 + ... +// Eg: "1x400G", 2x100G(4) + 1x200G(4)" +func ConfigFromBreakoutModeWithSkipLane(t *testing.T, dut *ondatra.DUTDevice, breakoutMode, port string, prevSkippedPorts []string, includeNewSkippdPorts bool) (*oc.Root, []string, error) { if len(breakoutMode) == 0 { - return nil, errors.Errorf("found empty breakout mode") + return nil, nil, errors.Errorf("found empty breakout mode") } // Check if requested port is a parent port. Breakout is applicable to parent port only. isParent, err := IsParentPort(t, dut, port) if err != nil { - return nil, errors.Wrap(err, "IsParentPort() failed") + return nil, nil, errors.Wrap(err, "IsParentPort() failed") } if !isParent { - return nil, errors.Errorf("port: %v is not a parent port", port) + return nil, nil, errors.Errorf("port: %v is not a parent port", port) } // Get lane number for port. slotPortLane, err := slotPortLaneForPort(port) if err != nil { - return nil, err + return nil, nil, err } currLaneNumber, err := strconv.Atoi(slotPortLane[laneIndex]) if err != nil { - return nil, errors.Wrapf(err, "failed to convert lane number (%v) to int", currLaneNumber) + return nil, nil, errors.Wrapf(err, "failed to convert lane number (%v) to int", currLaneNumber) } maxLanes, err := MaxLanesPerPort(t, dut) if err != nil { - return nil, errors.Wrap(err, "failed to fetch max lanes") + return nil, nil, errors.Wrap(err, "failed to fetch max lanes") } // For a mixed breakout mode, get "+" separated breakout groups. @@ -541,24 +583,25 @@ func ConfigFromBreakoutMode(t *testing.T, dut *ondatra.DUTDevice, breakoutMode, index := 0 breakoutGroups := make(map[uint8]*oc.Component_Port_BreakoutMode_Group) interfaceConfig := make(map[string]*oc.Interface) + var skippedPorts []string // For each breakout group, get numBreakouts and breakoutSpeed. Breakout group is in the format "numBreakouts x breakoutSpeed(numPhysicalChannels)" // Eg. 2x100G(4) for _, mode := range modes { values := strings.Split(mode, "x") if len(values) != 2 { - return nil, errors.Errorf("invalid breakout format (%v)", mode) + return nil, nil, errors.Errorf("invalid breakout format (%v)", mode) } numBreakouts, err := portStringToUint8(values[0]) if err != nil { - return nil, errors.Wrapf(err, "error parsing numBreakouts for breakout mode %v", mode) + return nil, nil, errors.Wrapf(err, "error parsing numBreakouts for breakout mode %v", mode) } u8numBreakouts := numBreakouts // Extract speed from breakout_speed(num_physical_channels) eg:100G(4) speed := strings.Split(values[1], "(") breakoutSpeed, ok := stringToEnumSpeedMap[speed[0]] if !ok { - return nil, errors.Errorf("found invalid breakout speed (%v) when parsing breakout mode %v", values[1], mode) + return nil, nil, errors.Errorf("found invalid breakout speed (%v) when parsing breakout mode %v", values[1], mode) } // Physical channels per breakout group are equally divided amongst breakouts in the group. numPhysicalChannels := maxChannelsInGroup / numBreakouts @@ -574,12 +617,41 @@ func ConfigFromBreakoutMode(t *testing.T, dut *ondatra.DUTDevice, breakoutMode, // Index is strictly ordered staring from 0. breakoutGroups[currIndex] = &group + var allInterfaces []string + scanCurrentLaneNumer := currLaneNumber + for i := 1; i <= numBreakouts; i++ { + intfName := fmt.Sprintf("%s%s/%s/%d", FrontPanelPortPrefix, slotPortLane[slotIndex], slotPortLane[portIndex], scanCurrentLaneNumer) + allInterfaces = append(allInterfaces, intfName) + offset := int(maxChannelsInGroup) / numBreakouts + scanCurrentLaneNumer += offset + } + + if includeNewSkippedPorts { + // Prepare interfaces to be removed from config. + var randomLen int + // TODO: allow not skipping any ports when LED color is correctly set. + for { + randomLen = rand.Intn(len(allInterfaces) + 1) + if randomLen > 0 { + break + } + } + + randomIndex := rand.Perm(len(allInterfaces)) + slices.Sort(randomIndex[:randomLen]) + for _, index := range randomIndex[:randomLen] { + skippedPorts = append(skippedPorts, allInterfaces[index]) + } + } + // Get the interface config for all interfaces corresponding to current breakout group. for i := 1; i <= int(numBreakouts); i++ { intfName := fmt.Sprintf("%s%s/%s/%d", FrontPanelPortPrefix, slotPortLane[slotIndex], slotPortLane[portIndex], currLaneNumber) - interfaceConfig[intfName], err = interfaceConfigForPort(t, dut, intfName, breakoutSpeed, fecMode(breakoutSpeed, numPhysicalChannels)) - if err != nil { - return nil, err + if !slices.Contains(skippedPorts, intfName) { + interfaceConfig[intfName], err = interfaceConfigForPort(t, dut, intfName, breakoutSpeed, fecMode(breakoutSpeed, numPhysicalChannels)) + if err != nil { + return nil, nil, err + } } offset := int(maxChannelsInGroup) / int(numBreakouts) currLaneNumber += offset @@ -590,12 +662,22 @@ func ConfigFromBreakoutMode(t *testing.T, dut *ondatra.DUTDevice, breakoutMode, // Get port ID. frontPanelPortToIndexMap, err := FrontPanelPortToIndexMappingForDevice(t, dut) if err != nil { - return nil, errors.Errorf("failed to fetch front panel port to index mapping from device %v", testhelperDUTNameGet(dut)) + return nil, nil, errors.Errorf("failed to fetch front panel port to index mapping from device %v", testhelperDUTNameGet(dut)) } - if _, ok := frontPanelPortToIndexMap[port]; !ok { - return nil, errors.Errorf("port %v not found in list of front panel port", port) + var portIndex int + if slices.Contains(skippedPorts, port) || slices.Contains(prevSkippedPorts, port) { + xcvrStr := strings.ReplaceAll(port, "Ethernet", "") + portIndexStr := xcvrStr[2 : len(xcvrStr)-2] + portIndex, err = strconv.Atoi(portIndexStr) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to convert port index (%v) to int", portIndexStr) + } + } else { + if _, ok := frontPanelPortToIndexMap[port]; !ok { + return nil, nil, errors.Errorf("port %v not found in list of front panel port", port) + } + portIndex = frontPanelPortToIndexMap[port] } - portIndex := frontPanelPortToIndexMap[port] // Construct component path config from created breakout groups. componentName := "1/" + strconv.Itoa(portIndex) @@ -613,7 +695,7 @@ func ConfigFromBreakoutMode(t *testing.T, dut *ondatra.DUTDevice, breakoutMode, Interface: interfaceConfig, Component: componentConfig, } - return deviceConfig, nil + return deviceConfig, skippedPorts, nil } // SpeedChangeOnlyPorts returns @@ -840,6 +922,7 @@ func BreakoutStateInfoForPort(t *testing.T, dut *ondatra.DUTDevice, port string, if portInfo == nil { return nil, errors.Errorf("got empty port information for breakout mode %v for port %v", currBreakoutMode, port) } + FilterSkippedPorts(t, dut, port, portInfo) // Get physical channels and operational statuses for list of ports in given breakout mode. for p := range portInfo { physicalChannels := testhelperIntfPhysicalChannelsGet(t, dut, p) @@ -850,6 +933,32 @@ func BreakoutStateInfoForPort(t *testing.T, dut *ondatra.DUTDevice, port string, return portInfo, nil } +// FilterSkippedPorts filters out ports that are skipped from the portInfo map. +func FilterSkippedPorts(t *testing.T, dut *ondatra.DUTDevice, port string, + portInfo map[string]*PortBreakoutInfo) { + if dut == nil { + return + } + for p, pInfo := range portInfo { + if testhelperIntfLookup(t, dut, p).IsPresent() { + physicalChannels := testhelperIntfPhysicalChannelsGet(t, dut, p) + if !slices.Equal(physicalChannels, pInfo.PhysicalChannels) { + t.Fatalf("physical channels mismatch for port on switch, get: %v, want: %v", + physicalChannels, pInfo.PhysicalChannels) + } + portSpeed := testhelperStatePortSpeedGet(t, dut, p) + if portSpeed != pInfo.PortSpeed { + t.Fatalf("port speed mismatch for port on switch, get: %v, want: %v", + portSpeed, pInfo.PortSpeed) + } + } else { + // Filter out skipped + delete(portInfo, p) + t.Logf("Port %v skipped, port Info: %v", p, pInfo) + } + } +} + // WaitForInterfaceState polls interface oper-status until it matches the expected oper-status. func WaitForInterfaceState(t *testing.T, dut *ondatra.DUTDevice, intfName string, expectedOperSatus oc.E_Interface_OperStatus, timeout time.Duration) error { t.Helper() @@ -916,6 +1025,90 @@ func PortPMDFromModel(t *testing.T, dut *ondatra.DUTDevice, port string) (string return testhelperPortPmdTypeGet(t, dut, port) } +// SetLinkEventDampingConfig sets the link event damping config on the DUT to the given config. +// penalty-based-aied is used if holdTime is -1. +func SetLinkEventDampingConfig(t *testing.T, dut *ondatra.DUTDevice, intf string, linkDampingConfig LinkDampingConfig) { + t.Helper() + // Configure the link event damping config. + if linkDampingConfig.HoldTime >= 0 { + // Use hold-time. + testhelperReplaceUint32(t, dut, gnmi.OC().Interface(intf).HoldTime().Up().Config(), uint32(linkDampingConfig.HoldTime)) + testhelperAwaitUint32(t, dut, gnmi.OC().Interface(intf).HoldTime().Up().State(), configTimeout, uint32(linkDampingConfig.HoldTime)) + return + } + + // Use penalty-based-aied. + penaltyBasedAiedPath, _, err := ygnmi.ResolvePath(gnmi.OC().Interface(intf).PenaltyBasedAied().Config().PathStruct()) + if err != nil { + t.Errorf("Failed to resolve penalty-based-aied path: %v", err) + return + } + setRequest := &gpb.SetRequest{ + Prefix: &gpb.Path{Origin: "openconfig", Target: testhelperDUTNameGet(dut)}, + Update: []*gpb.Update{{ + Path: penaltyBasedAiedPath, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: []byte(fmt.Sprintf(linkDampingPayload, linkDampingConfig.MaxSuppressTime, linkDampingConfig.DecayHalfLife, linkDampingConfig.SuppressThreshold, linkDampingConfig.ReuseThreshold, linkDampingConfig.FlapPenalty)), + }, + }, + }}, + } + gnmiSet(t, dut, setRequest) + testhelperAwaitUint32(t, dut, gnmi.OC().Interface(intf).PenaltyBasedAied().MaxSuppressTime().State(), configTimeout, linkDampingConfig.MaxSuppressTime) + testhelperAwaitUint32(t, dut, gnmi.OC().Interface(intf).PenaltyBasedAied().DecayHalfLife().State(), configTimeout, linkDampingConfig.DecayHalfLife) + testhelperAwaitUint32(t, dut, gnmi.OC().Interface(intf).PenaltyBasedAied().SuppressThreshold().State(), configTimeout, linkDampingConfig.SuppressThreshold) + testhelperAwaitUint32(t, dut, gnmi.OC().Interface(intf).PenaltyBasedAied().ReuseThreshold().State(), configTimeout, linkDampingConfig.ReuseThreshold) + testhelperAwaitUint32(t, dut, gnmi.OC().Interface(intf).PenaltyBasedAied().FlapPenalty().State(), configTimeout, linkDampingConfig.FlapPenalty) +} + +// DisableLinkEventDampingConfig disables the link event damping config on the DUT. +func DisableLinkEventDampingConfig(t *testing.T, dut *ondatra.DUTDevice, intf string) { + t.Helper() + initialConfig := FetchLinkEventDampingState(t, dut, intf) + if initialConfig.HoldTime >= 0 { + // Use hold-time. + SetLinkEventDampingConfig(t, dut, intf, LinkDampingConfig{HoldTime: 0}) + } else { + // Use penalty-based-aied. + SetLinkEventDampingConfig(t, dut, intf, LinkDampingConfig{HoldTime: -1, MaxSuppressTime: 0, DecayHalfLife: 0, SuppressThreshold: 0, ReuseThreshold: 0, FlapPenalty: 0}) + } +} + +// FetchLinkEventDampingState fetches the link event damping state from the DUT. +func FetchLinkEventDampingState(t *testing.T, dut *ondatra.DUTDevice, intf string) LinkDampingConfig { + t.Helper() + config := LinkDampingConfig{} + // Lookup the hold time UP state value. + holdTimeUp, present := testhelperHoldTimeUpLookup(t, dut, intf) + if present { + config.HoldTime = int32(holdTimeUp) + return config + } + config.HoldTime = -1 + + // Hold time UP is not present, lookup the penalty-based-aied state values. + penaltyBasedAied, present := testhelperPenaltyBasedAiedLookup(t, dut, intf) + if present { + if penaltyBasedAied.MaxSuppressTime != nil { + config.MaxSuppressTime = *penaltyBasedAied.MaxSuppressTime + } + if penaltyBasedAied.DecayHalfLife != nil { + config.DecayHalfLife = *penaltyBasedAied.DecayHalfLife + } + if penaltyBasedAied.SuppressThreshold != nil { + config.SuppressThreshold = *penaltyBasedAied.SuppressThreshold + } + if penaltyBasedAied.ReuseThreshold != nil { + config.ReuseThreshold = *penaltyBasedAied.ReuseThreshold + } + if penaltyBasedAied.FlapPenalty != nil { + config.FlapPenalty = *penaltyBasedAied.FlapPenalty + } + } + return config +} + //Adding function to manually convert string to uint32 func lower(c byte) byte { return c | ('x' - 'X') diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper.go index 5f7a3abed3e..0a30f4c74ab 100644 --- a/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper.go +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/testhelper.go @@ -2,9 +2,9 @@ package testhelper import ( + "fmt" "crypto/rand" "math/big" - "fmt" "strings" "testing" "time" @@ -100,11 +100,9 @@ var ( } teardownDUTDeviceInfoGet = func(t *testing.T) DUTInfo { - dut := ondatra.DUT(t, "DUT") - return DUTInfo{ - name: dut.Name(), - vendor: dut.Vendor(), - } + dutName := dut.Name() + d := DUTInfo{name: dutName, vendor: dut.Vendor()} + return d } teardownDUTPeerDeviceInfoGet = func(t *testing.T) DUTInfo { @@ -113,13 +111,13 @@ var ( return DUTInfo{} } - if peer, ok := duts["CONTROL"]; ok { - return DUTInfo{ - name: peer.Name(), - vendor: peer.Vendor(), - } + peer, ok := duts["CONTROL"] + if !ok { + return DUTInfo{} } - return DUTInfo{} + controlName := peer.Name() + d := DUTInfo{name: controlName, vendor: peer.Vendor()} + return d } teardownDUTHealthzGet = func(t *testing.T) healthzpb.HealthzClient { @@ -158,6 +156,26 @@ var ( testhelperTransceiverEmpty = func(t *testing.T, d *ondatra.DUTDevice, port string) bool { return gnmi.Get(t, d, gnmi.OC().Component(port).Empty().State()) } + + testhelperHoldTimeUpLookup = func(t *testing.T, d *ondatra.DUTDevice, port string) (uint32, bool) { + resp, present := gnmi.Lookup(t, d, gnmi.OC().Interface(port).HoldTime().State()).Val() + if !present || resp == nil || resp.Up == nil { + return 0, false + } + return *resp.Up, true + } + + testhelperPenaltyBasedAiedLookup = func(t *testing.T, d *ondatra.DUTDevice, port string) (*oc.Interface_PenaltyBasedAied, bool) { + return gnmi.Lookup(t, d, gnmi.OC().Interface(port).PenaltyBasedAied().State()).Val() + } + + testhelperReplaceUint32 = func(t *testing.T, d *ondatra.DUTDevice, path ygnmi.ConfigQuery[uint32], value uint32) *ygnmi.Result { + return gnmi.Replace(t, d, path, value) + } + + testhelperAwaitUint32 = func(t *testing.T, d *ondatra.DUTDevice, path ygnmi.SingletonQuery[uint32], timeout time.Duration, value uint32) *ygnmi.Value[uint32] { + return gnmi.Await(t, d, path, timeout, value) + } ) // FrontPanelPortPrefix defines prefix string for front panel ports. @@ -218,23 +236,24 @@ type TearDownOptions struct { } // NewTearDownOptions creates the TearDownOptions structure with default values. -func NewTearDownOptions(t *testing.T) TearDownOptions { - return TearDownOptions{ +func NewTearDownOptions(t *testing.T) *TearDownOptions { + o := &TearDownOptions{ StartTime: time.Now(), DUTName: teardownDUTNameGet(t), DUTDeviceInfo: teardownDUTDeviceInfoGet(t), DUTPeerDeviceInfo: teardownDUTPeerDeviceInfoGet(t), } + return o } // WithID attaches an ID to the test. -func (o TearDownOptions) WithID(id string) TearDownOptions { +func (o *TearDownOptions) WithID(id string) *TearDownOptions { o.IDs = append(o.IDs, id) return o } // WithIDs attaches a list of IDs to the test. -func (o TearDownOptions) WithIDs(ids []string) TearDownOptions { +func (o *TearDownOptions) WithIDs(ids []string) *TearDownOptions { for _, id := range ids { o.IDs = append(o.IDs, id) } @@ -245,11 +264,19 @@ func (o TearDownOptions) WithIDs(ids []string) TearDownOptions { // of the reserved devices on teardown. // Accepts a list of paths to ignore while checking for config changes. // The test will fail if the config is not restored. -func (o TearDownOptions) WithConfigRestorer(t *testing.T, ignorePaths []string) TearDownOptions { +func (o *TearDownOptions) WithConfigRestorer(t *testing.T, ignorePaths []string) *TearDownOptions { o.configRestorer = NewConfigRestorerWithIgnorePaths(t, ignorePaths) return o } +// RestoreConfigs restores the configs of the reserved devices. +func (o *TearDownOptions) RestoreConfigs(t *testing.T) error { + if o.configRestorer == nil { + return fmt.Errorf("configRestorer was not initialized") + } + return o.configRestorer.RestoreConfigs(t) +} + // TearDown provides an interface to implement the teardown routine. type TearDown interface { Teardown(t *testing.T) diff --git a/sdn_tests/pins_ondatra/infrastructure/testhelper/utils.go b/sdn_tests/pins_ondatra/infrastructure/testhelper/utils.go new file mode 100644 index 00000000000..f10e2c92b55 --- /dev/null +++ b/sdn_tests/pins_ondatra/infrastructure/testhelper/utils.go @@ -0,0 +1,36 @@ +package testhelper + +import ( + "context" + "fmt" + "time" + + log "github.com/golang/glog" +) + +type pollStatus bool + +const ( + continuePoll pollStatus = false + exitPoll pollStatus = true +) + +// pollFunc returns true if the condition is met. +type pollFunc func() pollStatus + +// poll polls the condition until it is met or the context is done. +func poll(ctx context.Context, pollInterval time.Duration, pf pollFunc) error { + ticker := time.NewTicker(pollInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return fmt.Errorf("polling for condition timed out, err: %v", ctx.Err()) + case <-ticker.C: + if pf() == exitPoll { + log.InfoContextf(ctx, "polling done") + return nil + } + } + } +} diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/BUILD.bazel b/sdn_tests/pins_ondatra/infrastructure/thinkit/BUILD.bazel index c9e3ad26d35..1bdb74bcd44 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/BUILD.bazel +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/BUILD.bazel @@ -20,17 +20,26 @@ package( licenses = ["notice"], ) +cc_library( + name = "thinkit_go_hdr", + testonly = True, + hdrs = ["thinkit_go_interface.h"], +) + go_library( name = "thinkit", testonly = True, srcs = ["thinkit.go"], + cdeps = [":thinkit_go_hdr"], cgo = True, importpath = "github.com/sonic-net/sonic-mgmt/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit", deps = [ "//infrastructure/binding:pinsbind", + "//infrastructure/testhelper", "@com_github_golang_glog//:glog", "@com_github_openconfig_gnmi//errlist", "@com_github_openconfig_ondatra//binding", + "@com_github_openconfig_ondatra//fakebind", "@com_github_openconfig_ondatra//proto:go_default_library", "@com_github_openconfig_ondatra//proxy", "@com_github_openconfig_ondatra//proxy/proto/reservation:go_default_library", @@ -54,13 +63,16 @@ cc_library( hdrs = ["thinkit.h"], deps = [ ":thinkit_cgo.cc", + ":thinkit_go_hdr", # keep "@com_github_sonic_net_sonic_pins//gutil:status", "@com_github_openconfig_ondatra//proto:ondatra_cc_proto", "@com_github_openconfig_ondatra//proxy/proto:reservation_cc_proto", + "@com_google_absl//absl/log", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/time", + "@com_google_googletest//:gtest", "@com_google_protobuf//:protobuf", ], ) @@ -117,6 +129,7 @@ cc_test( deps = [ ":cthinkit", ":ondatra_generic_testbed_fixture", + ":thinkit_go_hdr", "@com_github_sonic_net_sonic_pins//gutil:proto_matchers", "@com_github_sonic_net_sonic_pins//gutil:status", "@com_github_sonic_net_sonic_pins//gutil:status_matchers", diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.cc b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.cc index 93617de1363..cde5ed8fa6c 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.cc +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.cc @@ -183,6 +183,7 @@ GetSutInterfaceInfoFromReservation( } // namespace void OndatraGenericTestbedFixture::TearDown() { + ondatra_hooks_.teardown(teardown_handler_id_); EXPECT_OK(ondatra_hooks_.release()); } @@ -197,6 +198,19 @@ OndatraGenericTestbedFixture::GetTestbedWithRequirements( ASSIGN_OR_RETURN(reservation::Reservation reservation, ondatra_hooks_.testbed()); + // Setup testhelper teardown. + teardown_handler_id_ = ondatra_hooks_.new_teardown_handler(/*opts=*/{ + .with_config_restorer = false, + }); + auto set_test_case_ids = + [this](const std::vector& test_case_ids) { + for (const std::string& test_case_id : test_case_ids) { + std::string test_case_id_go_str = test_case_id; + ondatra_hooks_.add_test_case_id(teardown_handler_id_, + test_case_id_go_str.data()); + } + }; + ASSIGN_OR_RETURN(auto sut_interface_info, GetSutInterfaceInfoFromReservation( testbed_request, reservation)); ASSIGN_OR_RETURN(const reservation::ResolvedDevice* dut, @@ -209,7 +223,7 @@ OndatraGenericTestbedFixture::GetTestbedWithRequirements( if (!control.ok()) { return std::make_unique( std::move(sut), /*control_device=*/nullptr, - std::move(sut_interface_info)); + std::move(sut_interface_info), set_test_case_ids); } ASSIGN_OR_RETURN(auto control_switch, CreateSwitchFromDevice(*(*control))); @@ -224,9 +238,10 @@ OndatraGenericTestbedFixture::GetTestbedWithRequirements( std::move(*response.mutable_config()->mutable_p4info()))); auto control_device_pointer = std::make_unique(std::move(control_device)); + return std::make_unique( std::move(sut), std::move(control_device_pointer), - std::move(sut_interface_info)); + std::move(sut_interface_info), set_test_case_ids); } } // namespace pins_test diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.h b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.h index 5710be92510..14cf6d97b5a 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.h +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture.h @@ -44,6 +44,7 @@ class OndatraGenericTestbedFixture : public thinkit::GenericTestbedInterface { private: OndatraHooks ondatra_hooks_; + int teardown_handler_id_ = 0; }; } // namespace pins_test diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture_test.cc b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture_test.cc index 03367501612..a7b153c67b5 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture_test.cc +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_generic_testbed_fixture_test.cc @@ -40,6 +40,7 @@ #include "gutil/status_matchers.h" #include "gutil/testing.h" #include "infrastructure/thinkit/thinkit.h" +#include "infrastructure/thinkit/thinkit_go_interface.h" #include "p4/v1/p4runtime.grpc.pb.h" #include "p4/v1/p4runtime.pb.h" #include "proto/gnmi/gnmi.grpc.pb.h" @@ -165,6 +166,9 @@ TEST_P(OndatraGenericTestbedFixtureTest, Test) { Call(EqualsProto(GetParam().expected_testbed_request), _, _)) .WillOnce(Return(absl::OkStatus())); MockFunction()> mock_testbed; + MockFunction + mock_new_teardown_handler; + MockFunction mock_teardown; std::list fake_switches; if (GetParam().returned_reservation.ok()) { reservation::Reservation reservation = *GetParam().returned_reservation; @@ -180,6 +184,8 @@ TEST_P(OndatraGenericTestbedFixtureTest, Test) { absl::StrCat("[::]:", fake_switch.GetPort())); } EXPECT_CALL(mock_testbed, Call).WillOnce(Return(reservation)); + EXPECT_CALL(mock_new_teardown_handler, Call(_)).WillOnce(Return(0)); + EXPECT_CALL(mock_teardown, Call(_)).WillOnce(Return()); } else { EXPECT_CALL(mock_testbed, Call) .WillOnce(Return(GetParam().returned_reservation)); @@ -187,10 +193,12 @@ TEST_P(OndatraGenericTestbedFixtureTest, Test) { MockFunction mock_release; EXPECT_CALL(mock_release, Call).WillOnce(Return(absl::OkStatus())); - OndatraGenericTestbedFixture fixture( - OndatraHooks{.init = mock_init.AsStdFunction(), - .testbed = mock_testbed.AsStdFunction(), - .release = mock_release.AsStdFunction()}); + OndatraGenericTestbedFixture fixture(OndatraHooks{ + .init = mock_init.AsStdFunction(), + .testbed = mock_testbed.AsStdFunction(), + .release = mock_release.AsStdFunction(), + .new_teardown_handler = mock_new_teardown_handler.AsStdFunction(), + .teardown = mock_teardown.AsStdFunction()}); fixture.SetUp(); if (GetParam().expected_sut_interface_info.ok()) { diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.cc b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.cc index 337b7ebc16a..6942bc018f8 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.cc +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "absl/status/status.h" #include "absl/status/statusor.h" @@ -128,8 +129,21 @@ void OndatraMirrorTestbedFixture::SetUp() { ASSERT_OK_AND_ASSIGN(std::unique_ptr control, CreateSwitchFromDevice(*control_device)); - mirror_testbed_ = std::make_unique(std::move(sut), - std::move(control)); + // Setup testhelper teardown. + teardown_handler_id_ = ondatra_hooks_.new_teardown_handler(/*opts=*/{ + .with_config_restorer = false, + }); + auto set_test_case_ids = + [this](const std::vector& test_case_ids) { + for (const std::string& test_case_id : test_case_ids) { + std::string test_case_id_go_str = test_case_id; + this->ondatra_hooks_.add_test_case_id(this->teardown_handler_id_, + test_case_id_go_str.data()); + } + }; + + mirror_testbed_ = std::make_unique( + std::move(sut), std::move(control), set_test_case_ids); } thinkit::MirrorTestbed& OndatraMirrorTestbedFixture::GetMirrorTestbed() { diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.h b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.h index 165736ef0ce..4593d70b611 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.h +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/ondatra_mirror_testbed_fixture.h @@ -43,6 +43,7 @@ class OndatraMirrorTestbedFixture : public thinkit::MirrorTestbedInterface { private: OndatraHooks ondatra_hooks_; + int teardown_handler_id_ = 0; std::unique_ptr mirror_testbed_ = nullptr; }; diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.cc b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.cc index 767eab7687d..213bc267f4c 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.cc +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.cc @@ -15,8 +15,10 @@ #include "infrastructure/thinkit/thinkit.h" #include +#include #include +#include "absl/log/log.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" @@ -24,8 +26,12 @@ #include "github.com/openconfig/ondatra/proto/testbed.pb.h" #include "github.com/openconfig/ondatra/proxy/proto/reservation.pb.h" #include "google/protobuf/message.h" +#include "google/protobuf/struct.pb.h" +#include "google/protobuf/util/json_util.h" +#include "gtest/gtest.h" #include "gutil/status.h" #include "infrastructure/thinkit/thinkit_cgo.h" +#include "infrastructure/thinkit/thinkit_go_interface.h" namespace pins_test { namespace { @@ -88,4 +94,18 @@ absl::Status OndatraRelease() { return FromErrorMessage(error_message); } +namespace testhelper { + +int NewTeardownOptions(struct testhelper_TeardownCreateOpts opts) { + return testhelperNewTearDownOptions(opts); +} + +void AddTestCaseID(int handler, char* test_case_id) { + testhelperAddTestCaseID(handler, test_case_id); +} + +void Teardown(int handler) { testhelperTeardown(handler); } + +void SaveSwitchLogs() { testhelperSaveSwitchLogs(); } +} // namespace testhelper } // namespace pins_test diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.go b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.go index a01454ce233..0bb028d4ab0 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.go +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.go @@ -16,31 +16,8 @@ package main /* -#include -#include +#include "infrastructure/thinkit/thinkit_go_interface.h" -struct ProtoIn { - const char* data; - size_t length; -}; - -struct ProtoOut { - void* proto; - void (*write_proto)(void* proto, char* data, size_t length); -}; - -struct StringOut { - void* string; - char* (*resize)(void* string, size_t length); -}; - -static void write_proto(struct ProtoOut proto_out, char* data, size_t length) { - proto_out.write_proto(proto_out.proto, data, length); -} - -static char* resize_string(struct StringOut string_out, size_t length) { - return string_out.resize(string_out.string, length); -} */ import ( "C" @@ -50,6 +27,7 @@ import ( "context" "fmt" "sync" + "testing" "time" "unsafe" @@ -59,10 +37,12 @@ import ( "github.com/openconfig/ondatra/binding" "github.com/openconfig/ondatra/proxy" "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" "google.golang.org/grpc" "google.golang.org/grpc/credentials/local" "google.golang.org/protobuf/proto" + "github.com/openconfig/ondatra/fakebind" opb "github.com/openconfig/ondatra/proto" rpb "github.com/openconfig/ondatra/proxy/proto/reservation" ) @@ -129,6 +109,7 @@ var ( p *proxy.Proxy resv *binding.Reservation b proxyBinding + f *fakebind.Binding // Used to set the reservation used by Ondatra APIs. newBinding = defaultBinding ) @@ -153,7 +134,15 @@ func Init(ctx context.Context, tb *opb.Testbed, waitTime, runTime C.long) error return err } p, err = proxy.New(b, grpc.Creds(local.NewCredentials())) - return err + + if err != nil { + return err + } + + // Set the reservation for Ondatra APIs. + f = fakebind.Setup().WithReservation(resv) + initGoTestHelpers() + return nil } func addProxyTarget(s *rpb.Service, proxyAddr string) error { @@ -224,3 +213,113 @@ func Release(ctx context.Context) error { p = nil return errs.Err() } + +type teardownHandler struct { + // Required to access ondatra APIs. + t *testing.T + // Handles the teardown logic for Ondatra tests. + o *testhelper.TearDownOptions + // Options used while creating the handler. + createOpts C.struct_testhelper_TeardownCreateOpts +} + +var ( + handlers = map[int]*teardownHandler{} + handlersGuard = sync.Mutex{} + newHandlerID = 1 +) + +func initGoTestHelpers() { + // Execute the function only once. + sync.OnceFunc(func() { + // Initialize the testing.T to access GO test helpers. + testing.Init() + })() +} + +//export testhelperNewTearDownOptions +func testhelperNewTearDownOptions(opts C.struct_testhelper_TeardownCreateOpts) C.int { + // Create a new testing.T to access ondatra APIs. + t := &testing.T{} + // TeardownOptions handles the teardown logic for Ondatra tests. + o := testhelper.NewTearDownOptions(t) + // Set the test case ID if provided. + id := C.GoString(opts.id) + if id != "" { + o = o.WithID(id) + } + // Set the teardown options. + if opts.with_config_restorer { + o = o.WithConfigRestorer(t, nil /*(ignorePaths)*/) + } + + h := &teardownHandler{ + t: &testing.T{}, + o: o, + createOpts: opts, + } + + // Acquire the lock to save the handler in a map. + handlersGuard.Lock() + defer handlersGuard.Unlock() + // Assign a unique ID to the handler. + hID := newHandlerID + newHandlerID++ + + // Save the handler in the map. + handlers[hID] = h + log.InfoContextf(context.TODO(), "Created new testhelper teardown handler, with id: %v", hID) + // Return the handler ID. + // Handler ID is used to identify the handler coming from the CC code. + return C.int(hID) +} + +//export testhelperTeardown +func testhelperTeardown(handlerID C.int) { + hID := int(handlerID) + + // Acquire the lock to read/remove the handler from the map. + handlersGuard.Lock() + defer handlersGuard.Unlock() + + h, ok := handlers[hID] + if !ok { + log.FatalContextf(context.TODO(), "handler %v not found", hID) + } + + o := h.o + // Trigger the teardown logic. + o.Teardown(h.t) + // Explicitly restore configs if enabled, + // as config restorer is not triggered in the teardown logic. + if h.createOpts.with_config_restorer { + if err := o.RestoreConfigs(h.t); err != nil { + log.WarningContextf(context.TODO(), "testhelperTeardown, failed to restore configs: %v", err) + } + } + + // Remove the handler from the map. + delete(handlers, hID) +} + +//export testhelperAddTestCaseID +func testhelperAddTestCaseID(handlerID C.int, testCaseID *C.char) { + if testCaseID == nil { + return + } + + // Get the handler from the map. + hID := int(handlerID) + handlersGuard.Lock() + defer handlersGuard.Unlock() + h, ok := handlers[hID] + if !ok { + log.FatalContextf(context.TODO(), "handler %v not found", hID) + } + h.o.WithID(C.GoString(testCaseID)) +} + +//export testhelperSaveSwitchLogs +func testhelperSaveSwitchLogs() { + log.Warningf("testhelperSaveSwitchLogs is unimplemented") +} diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.h b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.h index 7067d41b718..4b5e57317b5 100644 --- a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.h +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit.h @@ -22,6 +22,7 @@ #include "absl/time/time.h" #include "github.com/openconfig/ondatra/proto/testbed.pb.h" #include "github.com/openconfig/ondatra/proxy/proto/reservation.pb.h" +#include "infrastructure/thinkit/thinkit_go_interface.h" namespace pins_test { @@ -32,6 +33,21 @@ absl::StatusOr OndatraTestbed(); absl::Status OndatraRelease(); +namespace testhelper { +// Creates a new GO teardown and returns a handler for it. +int NewTeardownOptions(struct testhelper_TeardownCreateOpts opts); + +// Runs the GO teardown for the handler. +void Teardown(int handler); + +// Saves the switch logs. +void SaveSwitchLogs(); + +// Associates a test case ID with the test. +void AddTestCaseID(int handler, char* test_case_id); + +} // namespace testhelper + struct OndatraHooks { std::function @@ -39,6 +55,13 @@ struct OndatraHooks { std::function()> testbed = OndatraTestbed; std::function release = OndatraRelease; + + std::function + new_teardown_handler = testhelper::NewTeardownOptions; + std::function teardown = testhelper::Teardown; + std::function save_switch_logs = testhelper::SaveSwitchLogs; + std::function add_test_case_id = + testhelper::AddTestCaseID; }; } // namespace pins_test diff --git a/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit_go_interface.h b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit_go_interface.h new file mode 100644 index 00000000000..29aae468238 --- /dev/null +++ b/sdn_tests/pins_ondatra/infrastructure/thinkit/thinkit_go_interface.h @@ -0,0 +1,49 @@ +#ifndef PINS_INFRASTRUCTURE_THINKIT_THINKIT_GO_INTERFACE_H_ +#define PINS_INFRASTRUCTURE_THINKIT_THINKIT_GO_INTERFACE_H_ + +// The header is shared between the CC and GO libraries. + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct ProtoIn { + const char* data; + size_t length; +}; + +struct ProtoOut { + void* proto; + void (*write_proto)(void* proto, char* data, size_t length); +}; + +struct StringOut { + void* string; + char* (*resize)(void* string, size_t length); +}; + +static void write_proto(struct ProtoOut proto_out, char* data, size_t length) { + proto_out.write_proto(proto_out.proto, data, length); +} + +static char* resize_string(struct StringOut string_out, size_t length) { + return string_out.resize(string_out.string, length); +} + +// Options for creating a new teardown. +// Used to create TearDownOptions defined in testhelper.go. +struct testhelper_TeardownCreateOpts { + char* id; + bool with_config_restorer; +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // PINS_INFRASTRUCTURE_THINKIT_THINKIT_GO_INTERFACE_H_ diff --git a/sdn_tests/pins_ondatra/tests/thinkit/BUILD.bazel b/sdn_tests/pins_ondatra/tests/thinkit/BUILD.bazel index 2c36d9d47f3..39618a0e4ad 100644 --- a/sdn_tests/pins_ondatra/tests/thinkit/BUILD.bazel +++ b/sdn_tests/pins_ondatra/tests/thinkit/BUILD.bazel @@ -5,6 +5,18 @@ package( licenses = ["notice"], ) +cc_library( + name = "util", + testonly = 1, + srcs = ["util.cc"], + hdrs = ["util.h"], + deps = [ + "@com_github_sonic_net_sonic_pins//gutil:testing", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", + "@com_google_absl//absl/flags:flag", + ], +) + cc_test( name = "random_blackbox_events_test", timeout = "long", @@ -42,15 +54,13 @@ cc_test( "notap", ], deps = [ - "@com_github_sonic_net_sonic_pins//gutil:testing", + ":util", "//infrastructure/thinkit:ondatra_params", - "@com_github_sonic_net_sonic_pins//p4_pdpi:p4_runtime_session", "@com_github_sonic_net_sonic_pins//tests/forwarding:arbitration_test", - "@com_github_sonic_net_sonic_pins//thinkit:mirror_testbed", "@com_github_sonic_net_sonic_pins//thinkit:mirror_testbed_fixture", + "@com_github_google_glog//:glog", "@com_github_grpc_grpc//:grpc++", "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", - "@com_google_absl//absl/flags:flag", "@com_google_googletest//:gtest_main", ], linkopts = ["-lresolv"], @@ -97,14 +107,79 @@ cc_test( ], deps = [ "@com_github_sonic_net_sonic_pins//dvaas:test_vector_cc_proto", +# "@com_github_sonic_net_sonic_pins//gutil:status", "@com_github_sonic_net_sonic_pins//gutil:testing", "//infrastructure/thinkit:ondatra_params", "@com_github_sonic_net_sonic_pins//tests/forwarding:arriba_test", "@com_github_sonic_net_sonic_pins//thinkit:mirror_testbed_fixture", "@com_github_google_glog//:glog", "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_googletest//:gtest_main", ], linkopts = ["-lresolv"], ) + +cc_test( + name = "packet_forwarding_test", + timeout = "eternal", + srcs = ["packet_forwarding_tests.cc"], + linkstatic = 1, + tags = [ + "exclusive", + "external", + "guitar", + "manual", + "notap", + ], + deps = [ + ":util", + "//infrastructure/thinkit:ondatra_params", + "@com_github_sonic_net_sonic_pins//tests/integration/system:packet_forwarding_tests", + "@com_github_sonic_net_sonic_pins//thinkit:generic_testbed_fixture", + "@com_google_absl//absl/log", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "l3_admit_test", + srcs = ["l3_admit_test.cc"], + tags = [ + "exclusive", + "external", + "guitar", + "manual", + "notap", + ], + deps = [ + ":util", + "//infrastructure/thinkit:ondatra_params", + "@com_github_sonic_net_sonic_pins//tests/forwarding:l3_admit_test", + "@com_github_sonic_net_sonic_pins//thinkit:mirror_testbed_fixture", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", + "@com_google_absl//absl/log", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "smoke_test", + srcs = ["smoke_test.cc"], + tags = [ + "exclusive", + "external", + "guitar", + "manual", + "notap", + ], + deps = [ + ":util", + "//infrastructure/thinkit:ondatra_params", + "@com_github_sonic_net_sonic_pins//tests/forwarding:smoke_test", + "@com_github_sonic_net_sonic_pins//thinkit:mirror_testbed_fixture", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdn_tests/pins_ondatra/tests/thinkit/arbitration_test.cc b/sdn_tests/pins_ondatra/tests/thinkit/arbitration_test.cc index d063d21c118..9dc5a78edf8 100644 --- a/sdn_tests/pins_ondatra/tests/thinkit/arbitration_test.cc +++ b/sdn_tests/pins_ondatra/tests/thinkit/arbitration_test.cc @@ -1,33 +1,31 @@ #include "tests/forwarding/arbitration_test.h" #include -#include #include -#include "absl/flags/flag.h" +#include "glog/logging.h" #include "gtest/gtest.h" #include "infrastructure/thinkit/ondatra_params.h" #include "p4/v1/p4runtime.pb.h" -#include "p4_pdpi/p4_runtime_session.h" -#include "thinkit/mirror_testbed.h" +#include "tests/thinkit/util.h" #include "thinkit/mirror_testbed_fixture.h" -ABSL_FLAG( - std::string, p4info_file, "", - "Path to the file containing the textproto of the P4Info to be pushed"); - namespace pins { namespace { INSTANTIATE_TEST_SUITE_P( pinsArbitrationTest, ArbitrationTestFixture, testing::Values([]() { - thinkit::MirrorTestbedFixtureParams mirror_testbed_params = - *pins::GetOndatraMirrorTestbedFixtureParams(); + absl::StatusOr params = + pins::GetOndatraMirrorTestbedFixtureParams(); + if (!params.ok()) { + LOG(FATAL) << "Failed to fetch params, status: " // Crash OK + << params.status(); + } return ArbitrationTestParams{ .mirror_testbed = std::shared_ptr( - mirror_testbed_params.mirror_testbed), - .gnmi_config = std::move(mirror_testbed_params.gnmi_config), - .p4info = std::move(mirror_testbed_params.p4_info)}; + params->mirror_testbed), + .gnmi_config = std::move(params->gnmi_config), + .p4info = pins_test::GetP4InfoFromFlag()}; }())); } // namespace diff --git a/sdn_tests/pins_ondatra/tests/thinkit/arriba_test.cc b/sdn_tests/pins_ondatra/tests/thinkit/arriba_test.cc index b75b56f0c95..1b3663fc295 100644 --- a/sdn_tests/pins_ondatra/tests/thinkit/arriba_test.cc +++ b/sdn_tests/pins_ondatra/tests/thinkit/arriba_test.cc @@ -5,44 +5,105 @@ #include #include "absl/flags/flag.h" +#include "absl/status/status.h" #include "absl/status/statusor.h" #include "dvaas/test_vector.pb.h" #include "glog/logging.h" #include "gtest/gtest.h" +// #include "gutil/status.h" #include "gutil/testing.h" #include "infrastructure/thinkit/ondatra_params.h" #include "thinkit/mirror_testbed_fixture.h" ABSL_FLAG(std::vector, arriba_test_vector_files, {}, "Paths to files containing ArribaTestVector textprotos."); +ABSL_FLAG(double, expected_minimum_success_rate, 1.0, + "Expected minimum success rate for the packet vectors."); +ABSL_FLAG( + bool, wait_for_all_enabled_interfaces_to_be_up, true, + "If true, waits for all enabled ports to be up on SUT and control switch."); +ABSL_FLAG( + absl::Duration, max_expected_packet_in_flight_duration, absl::Seconds(3), + R"(Maximum time expected it takes to receive output packets either from SUT + or control switch in response to an injected input packet. Beyond that, + the input packet might be considered dropped.)"); namespace pins_test { namespace { +// Unsolicited packets that, for the time being, are acceptable in a PINS +// testbeds. +inline bool AlpineIsExpectedUnsolicitedPacket(const packetlib::Packet& packet) { + if (packet.headers().size() == 3 && + packet.headers(2).icmp_header().type() == "0x85") { + return true; + } + // TODO Switch generates IPV6 hop_by_hop packets. + if (packet.headers().size() == 2 && + packet.headers(1).ipv6_header().next_header() == "0x00") { + return true; + } + // Switch generates LACP packets if LAGs are present. + if (packet.headers().size() == 1 && + packet.headers(0).ethernet_header().ethertype() == "0x8809") { + return true; + } + // Alpine's deployment environment sends ARP packets. + if (!packet.headers().empty() && + packet.headers(0).ethernet_header().ethertype() == "0x0806") { + LOG(INFO) << "ALPINE: ARP packet"; + return true; + } + return false; +} + // Returns one test instance per test vector textproto file provided through the // `--arriba_test_vector_files` flag. -std::vector GetTestInstancesOrDie() { +absl::StatusOr> GetTestInstances() { // Make sure there is at least one test vector present. - CHECK(!absl::GetFlag(FLAGS_arriba_test_vector_files).empty()) - << "--arriba_test_vector_files is required."; + if (absl::GetFlag(FLAGS_arriba_test_vector_files).empty()) { + return absl::InvalidArgumentError( + "--arriba_test_vector_files is required."); + } - thinkit::MirrorTestbedFixtureParams mirror_testbed_params = - *pins::GetOndatraMirrorTestbedFixtureParams(); + ASSIGN_OR_RETURN(thinkit::MirrorTestbedFixtureParams mirror_testbed_params, + pins::GetOndatraMirrorTestbedFixtureParams()); std::vector test_instances; for (const std::string& test_vector_file : absl::GetFlag(FLAGS_arriba_test_vector_files)) { + dvaas::ArribaTestVector arriba_test_vector; + arriba_test_vector = + gutil::ParseProtoFileOrDie(test_vector_file); + test_instances.push_back(ArribaTestParams{ .mirror_testbed = std::shared_ptr( mirror_testbed_params.mirror_testbed), - .arriba_test_vector = - gutil::ParseProtoFileOrDie( - test_vector_file), + .arriba_test_vector = arriba_test_vector, + .validation_params = + { + .expected_minimum_success_rate = + absl::GetFlag(FLAGS_expected_minimum_success_rate), + .max_expected_packet_in_flight_duration = + absl::GetFlag(FLAGS_max_expected_packet_in_flight_duration), + .is_expected_unsolicited_packet = + AlpineIsExpectedUnsolicitedPacket, + }, + .wait_for_all_enabled_interfaces_to_be_up = + absl::GetFlag(FLAGS_wait_for_all_enabled_interfaces_to_be_up), + .description = test_vector_file, }); } return test_instances; } +std::vector GetTestInstancesOrDie() { + absl::StatusOr> test_instances = + GetTestInstances(); + CHECK_OK(test_instances.status()); // Crash OK. + return *test_instances; +} + INSTANTIATE_TEST_SUITE_P(pinsOndatraArribaTest, ArribaTest, testing::ValuesIn(GetTestInstancesOrDie())); diff --git a/sdn_tests/pins_ondatra/tests/thinkit/l3_admit_test.cc b/sdn_tests/pins_ondatra/tests/thinkit/l3_admit_test.cc new file mode 100644 index 00000000000..33243845372 --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/thinkit/l3_admit_test.cc @@ -0,0 +1,31 @@ +#include "tests/forwarding/l3_admit_test.h" + +#include + +#include "absl/log/log.h" +#include "absl/status/statusor.h" +#include "gtest/gtest.h" +#include "infrastructure/thinkit/ondatra_params.h" +#include "p4/v1/p4runtime.pb.h" +#include "tests/thinkit/util.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace pins { +namespace { + +L3AdmitTestParams GetTestParamsOrDie() { + absl::StatusOr params = + pins::GetOndatraMirrorTestbedFixtureParams(); + if (!params.ok()) { + LOG(FATAL) << "Failed to fetch params, status: " << params.status(); + } + return L3AdmitTestParams{ + .testbed_interface = std::move(params->mirror_testbed), + .p4info = pins_test::GetP4InfoFromFlag().value()}; +} + +INSTANTIATE_TEST_SUITE_P(pinsL3AdmitTest, L3AdmitTestFixture, + testing::Values(GetTestParamsOrDie())); + +} // namespace +} // namespace pins diff --git a/sdn_tests/pins_ondatra/tests/thinkit/packet_forwarding_tests.cc b/sdn_tests/pins_ondatra/tests/thinkit/packet_forwarding_tests.cc new file mode 100644 index 00000000000..424feae9202 --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/thinkit/packet_forwarding_tests.cc @@ -0,0 +1,32 @@ +#include "tests/integration/system/packet_forwarding_tests.h" + +#include + +#include "glog/logging.h" +#include "gtest/gtest.h" +#include "infrastructure/thinkit/ondatra_params.h" +#include "tests/thinkit/util.h" +#include "thinkit/generic_testbed_fixture.h" + +namespace pins_test { +namespace { + +INSTANTIATE_TEST_SUITE_P( + pinsPacketForwardingTest, + PacketForwardingTestFixture, + testing::Values([]() { + absl::StatusOr params = + pins::GetOndatraGenericTestbedFixtureParams(); + if (!params.ok()) { + LOG(FATAL) << "Failed to fetch params, status: " // Crash OK + << params.status(); + } + std::optional p4_info = + pins_test::GetP4InfoFromFlag(); + return PacketForwardingTestParams{ + .testbed_interface = params->testbed_interface, + .push_p4_info = p4_info.has_value(), + .p4_info = p4_info}; + }())); +} // namespace +} // namespace pins_test diff --git a/sdn_tests/pins_ondatra/tests/thinkit/random_blackbox_events_test.cc b/sdn_tests/pins_ondatra/tests/thinkit/random_blackbox_events_test.cc index 820ec352c03..5690a25eac4 100644 --- a/sdn_tests/pins_ondatra/tests/thinkit/random_blackbox_events_test.cc +++ b/sdn_tests/pins_ondatra/tests/thinkit/random_blackbox_events_test.cc @@ -1,3 +1,5 @@ +#include + #include "absl/status/statusor.h" #include "gtest/gtest.h" #include "infrastructure/thinkit/ondatra_params.h" @@ -8,8 +10,28 @@ namespace pins_test { namespace { INSTANTIATE_TEST_SUITE_P( - pinsIntegrationTest, RandomBlackboxEventsTest, - testing::Values(*pins::GetOndatraGenericTestbedFixtureParams())); + pinsIntegrationTest, RandomBlackboxEventsTest, testing::Values([] { + thinkit::GenericTestbedFixtureParams params = + *pins::GetOndatraGenericTestbedFixtureParams(); + return RandomBlackboxEventsTestParams{ + .testbed_interface = params.testbed_interface, + .p4_info = std::nullopt, + // TODO - Consider moving to standard fuzzer config. + .fuzzer_config_params = { + .qos_queues = {"0x0", "0x1", "0x2", "0x3", "0x4", "0x5", "0x6", + "0x7"}, + .role = "sdn_controller", + .mutate_update_probability = 0.1f, + .tables_for_which_to_not_exceed_resource_guarantees = + {"vrf_table", "mirror_session_table"}, + // TODO: Remove once P4RT translated types + // are supported by P4-constraints. + .ignore_constraints_on_tables = + { + "ingress.routing_lookup.vrf_table", + }, + }}; + }())); } // namespace } // namespace pins_test diff --git a/sdn_tests/pins_ondatra/tests/thinkit/smoke_test.cc b/sdn_tests/pins_ondatra/tests/thinkit/smoke_test.cc new file mode 100644 index 00000000000..74f408bd667 --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/thinkit/smoke_test.cc @@ -0,0 +1,23 @@ +#include "tests/forwarding/smoke_test.h" + +#include + +#include "gtest/gtest.h" +#include "infrastructure/thinkit/ondatra_params.h" +#include "tests/thinkit/util.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace pins_test { +namespace { +INSTANTIATE_TEST_SUITE_P( + pinsSmokeTest, SmokeTestFixture, + testing::Values(SmokeTestParams{ + .mirror_testbed = std::shared_ptr( + pins::GetOndatraMirrorTestbedFixtureParams() + .value() + .mirror_testbed), + .p4info = GetP4InfoFromFlag().value(), + .does_not_support_gre_tunnels = true, + })); +} // namespace +} // namespace pins_test diff --git a/sdn_tests/pins_ondatra/tests/thinkit/util.cc b/sdn_tests/pins_ondatra/tests/thinkit/util.cc new file mode 100644 index 00000000000..66dd34a7e80 --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/thinkit/util.cc @@ -0,0 +1,23 @@ +#include "tests/thinkit/util.h" + +#include +#include + +#include "absl/flags/flag.h" +#include "gutil/testing.h" + +ABSL_FLAG( + std::string, pins_p4info_file, "", + "Path to the file containing the textproto of the P4Info to be pushed"); + +namespace pins_test { + +std::optional GetP4InfoFromFlag() { + std::string p4info_file = absl::GetFlag(FLAGS_pins_p4info_file); + if (p4info_file.empty()) { + return std::nullopt; + } + return gutil::ParseProtoFileOrDie(p4info_file); +} + +} // namespace pins_test diff --git a/sdn_tests/pins_ondatra/tests/thinkit/util.h b/sdn_tests/pins_ondatra/tests/thinkit/util.h new file mode 100644 index 00000000000..c8a0190e1db --- /dev/null +++ b/sdn_tests/pins_ondatra/tests/thinkit/util.h @@ -0,0 +1,20 @@ +#ifndef PINS_TESTS_UTIL_H_ +#define PINS_TESTS_UTIL_H_ + +#include +#include + +#include "absl/flags/declare.h" +#include "p4/config/v1/p4info.pb.h" + +ABSL_DECLARE_FLAG(std::string, pins_p4info_file); + +namespace pins_test { + +// If the p4info_file flag is set, returns the P4Info from the file. +// Otherwise returns std::nullopt. +std::optional GetP4InfoFromFlag(); + +} // namespace pins_test + +#endif // PINS_TESTS_UTIL_H_