diff --git a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go b/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go index bdbb469bdc1..0c7f1460f0d 100644 --- a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go +++ b/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go @@ -23,14 +23,12 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" gpb "github.com/openconfig/gnmi/proto/gnmi" - gnps "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoigo/system" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ondatra/gnmi/oc/acl" - "github.com/openconfig/ondatra/gnoi" "github.com/openconfig/ondatra/ixnet" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" @@ -192,12 +190,6 @@ var ( IPv4Len: plenIPv4, IPv6Len: plenIPv6, } - routingDaemon = map[ondatra.Vendor]string{ - ondatra.JUNIPER: "rpd", - ondatra.ARISTA: "Bgp-main", - ondatra.CISCO: "emsd", - ondatra.NOKIA: "sr_bgp_mgr", - } ) func configureRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, name string, pr oc.E_RoutingPolicy_PolicyResultType) { @@ -652,56 +644,6 @@ func removeNewPeers(t *testing.T, dut *ondatra.DUTDevice, nbrs []*bgpNeighbor) { fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) } -func restartRoutingProcess(t *testing.T, dut *ondatra.DUTDevice) { - t.Helper() - if _, ok := routingDaemon[dut.Vendor()]; !ok { - t.Fatalf("Please add support for vendor %v in var routingDaemon", dut.Vendor()) - } - t.Run("KillGRIBIDaemon", func(t *testing.T) { - // Find the PID of routing Daemon. - var pId uint64 - pName := routingDaemon[dut.Vendor()] - t.Run("FindroutingDaemonPid", func(t *testing.T) { - pId = findProcessByName(t, dut, pName) - if pId == 0 { - t.Fatalf("Couldn't find pid of routing daemon '%s'", pName) - } else { - t.Logf("Pid of routing daemon '%s' is '%d'", pName, pId) - } - }) - - // Kill routing daemon through gNOI Kill Request. - t.Run("ExecuteGnoiKill", func(t *testing.T) { - // TODO - pid type is uint64 in oc-system model, but uint32 in gNOI Kill Request proto. - // Until the models are brought in line, typecasting the uint64 to uint32. - gNOIKillProcess(t, dut, pName, uint32(pId)) - // Wait for a bit for routing daemon on the DUT to restart. - time.Sleep(30 * time.Second) - }) - }) -} - -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(t *testing.T, dut *ondatra.DUTDevice, pName string) uint64 { - t.Helper() - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint64 - for _, proc := range pList { - if proc.GetName() == pName { - pID = proc.GetPid() - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } - } - return pID -} - -// gNOIKillProcess kills a daemon on the DUT, given its name and pid. -func gNOIKillProcess(t *testing.T, dut *ondatra.DUTDevice, pName string, pID uint32) { - t.Helper() - killResponse := gnoi.Execute(t, dut, system.NewKillProcessOperation().Name(pName).PID(pID).Signal(gnps.KillProcessRequest_SIGNAL_TERM).Restart(true)) - t.Logf("Got kill process response: %v\n\n", killResponse) -} - // setBgpPolicy is used to configure routing policy on DUT. func setBgpPolicy(t *testing.T, dut *ondatra.DUTDevice, d *oc.Root) { t.Helper() @@ -943,7 +885,7 @@ func TestTrafficWithGracefulRestartLLGR(t *testing.T) { }) t.Run("Restart routing", func(t *testing.T) { - restartRoutingProcess(t, dut) + gnoi.KillProcess(t, dut, gnoi.ROUTING, true) }) var bgpIxPeer []*ixnet.BGP diff --git a/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md b/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md index a27b42da6d7..6cd0a79e2a2 100644 --- a/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md +++ b/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md @@ -29,11 +29,14 @@ Ensure that gRIBI entries are persisted over daemon failure. * Issuing a gRIBI Get RPC results in 203.0.113.0/24 being returned. -## Protocol/RPC Parameter Coverage - -* gRIBI - * ModifyRequest - * GetRequest +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Telemetry Parameter Coverage diff --git a/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go b/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go index a07108c8c39..d0b63b30c00 100644 --- a/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go +++ b/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go @@ -23,16 +23,14 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/otgutils" - gnps "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoigo/system" - grps "github.com/openconfig/gribi/v1/proto/service" + grpb "github.com/openconfig/gribi/v1/proto/service" "github.com/openconfig/gribigo/fluent" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/gnoi" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" ) @@ -88,13 +86,6 @@ var ( IPv4: "192.0.2.6", IPv4Len: ipv4PrefixLen, } - - gRIBIDaemons = map[ondatra.Vendor]string{ - ondatra.ARISTA: "Gribi", - ondatra.CISCO: "emsd", - ondatra.JUNIPER: "rpd", - ondatra.NOKIA: "sr_grpc_server", - } ) // configInterfaceDUT configures the DUT interfaces. @@ -235,7 +226,7 @@ func verifyGRIBIGet(ctx context.Context, t *testing.T, clientA *gribi.Client, du entries := getResponse.GetEntry() var found bool for _, entry := range entries { - v := entry.Entry.(*grps.AFTEntry_Ipv4) + v := entry.Entry.(*grpb.AFTEntry_Ipv4) if prefix := v.Ipv4.GetPrefix(); prefix != "" { if prefix == ateDstNetCIDR { found = true @@ -249,36 +240,12 @@ func verifyGRIBIGet(ctx context.Context, t *testing.T, clientA *gribi.Client, du } } -// gNOIKillProcess kills a daemon on the DUT, given its name and pid. -func gNOIKillProcess(ctx context.Context, t *testing.T, args *testArgs, pName string, pID uint32) { - killResponse := gnoi.Execute(t, args.dut, system.NewKillProcessOperation().Name(pName).PID(pID).Signal(gnps.KillProcessRequest_SIGNAL_TERM).Restart(true)) - t.Logf("Got kill process response: %v\n\n", killResponse) -} - -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, pName string) uint64 { - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint64 - for _, proc := range pList { - if proc.GetName() == pName { - pID = proc.GetPid() - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } - } - return pID -} - func TestDUTDaemonFailure(t *testing.T) { start := time.Now() dut := ondatra.DUT(t, "dut") ctx := context.Background() - // Check if vendor specific gRIBI daemon name has been added to gRIBIDaemons var - if _, ok := gRIBIDaemons[dut.Vendor()]; !ok { - t.Fatalf("Please add support for vendor %v in var gRIBIDaemons", dut.Vendor()) - } - // Configure the DUT. t.Logf("Configure DUT") configureDUT(t, dut) @@ -343,30 +310,7 @@ func TestDUTDaemonFailure(t *testing.T) { }) t.Run("KillGRIBIDaemon", func(t *testing.T) { - - // Find the PID of gRIBI Daemon. - var pId uint64 - pName := gRIBIDaemons[dut.Vendor()] - t.Run("FindGRIBIDaemonPid", func(t *testing.T) { - - pId = findProcessByName(ctx, t, dut, pName) - if pId == 0 { - t.Fatalf("Couldn't find pid of gRIBI daemon '%s'", pName) - } else { - t.Logf("Pid of gRIBI daemon '%s' is '%d'", pName, pId) - } - }) - - // Kill gRIBI daemon through gNOI Kill Request. - t.Run("ExecuteGnoiKill", func(t *testing.T) { - // TODO - pid type is uint64 in oc-system model, but uint32 in gNOI Kill Request proto. - // Until the models are brought in line, typecasting the uint64 to uint32. - gNOIKillProcess(ctx, t, args, pName, uint32(pId)) - - // Wait for a bit for gRIBI daemon on the DUT to restart. - time.Sleep(30 * time.Second) - - }) + gnoi.KillProcess(t, dut, gnoi.GRIBI, true) t.Logf("Time check: %s", time.Since(start)) diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md b/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md index ce29ac4d93c..cd87a3ba503 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md +++ b/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md @@ -14,83 +14,78 @@ * Ensure that IS-IS adjacency is not coming up on the passive interface. * TODO-Verify the output of ST path displaying the interface as passive in ISIS database/adj table -## Config Parameter coverage +# OpenConfig Path and RPC Coverage +```yaml +paths: +# config +/network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: +/network-instances/network-instance/protocols/protocol/isis/global/config/net: +/network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: +/network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: +/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: +/network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: -* For prefix: +# isis telemetry +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/state/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-area : +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes : +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/config/passive - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/config/passive - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * interfaces/interface/state/passive - * interfaces/interface/levels/level/state/passive - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go b/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go index 48200bfbf3b..725c6fab80a 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go +++ b/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go @@ -58,6 +58,9 @@ func configureISIS(t *testing.T, ts *isissession.TestSession) { globalIsis.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) globalIsis.LevelCapability = oc.Isis_LevelType_LEVEL_2 globalIsis.AuthenticationCheck = ygot.Bool(true) + if deviations.ISISGlobalAuthenticationNotRequired(ts.DUT) { + globalIsis.AuthenticationCheck = nil + } globalIsis.HelloPadding = oc.Isis_HelloPaddingType_ADAPTIVE // Level configs. @@ -69,6 +72,11 @@ func configureISIS(t *testing.T, ts *isissession.TestSession) { auth.AuthMode = oc.IsisTypes_AUTH_MODE_MD5 auth.AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY auth.AuthPassword = ygot.String(password) + if deviations.ISISExplicitLevelAuthenticationConfig(ts.DUT) { + auth.DisableCsnp = ygot.Bool(false) + auth.DisableLsp = ygot.Bool(false) + auth.DisablePsnp = ygot.Bool(false) + } // Interface configs. intfName := ts.DUTPort1.Name() @@ -188,16 +196,20 @@ func TestIsisInterfacePassive(t *testing.T) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } // Checking dis system id. - if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { - t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { + t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + } } // Checking isis local extended circuit id. if got := gnmi.Get(t, ts.DUT, adjPath.LocalExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected local extended circuit id not found,expected non-zero value, got %d", got) } // Checking multitopology. - if got := gnmi.Get(t, ts.DUT, adjPath.MultiTopology().State()); got != false { - t.Errorf("FAIL- Expected value for multi topology not found, got %t, want %t", got, false) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.MultiTopology().State()); got != false { + t.Errorf("FAIL- Expected value for multi topology not found, got %t, want %t", got, false) + } } // Checking neighbor circuit type. if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { @@ -240,59 +252,81 @@ func TestIsisInterfacePassive(t *testing.T) { t.Errorf("FAIL- Restart support not present") } // Checking isis restart suppress. - if _, ok := gnmi.Lookup(t, ts.DUT, adjPath.RestartStatus().State()).Val(); !ok { - t.Errorf("FAIL- Restart suppress not present") + if !deviations.MissingValueForDefaults(ts.DUT) { + if _, ok := gnmi.Lookup(t, ts.DUT, adjPath.RestartStatus().State()).Val(); !ok { + t.Errorf("FAIL- Restart suppress not present") + } } }) t.Run("System level counter checks", func(t *testing.T) { // Checking authFail counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthFails().State()); got != 0 { - t.Errorf("FAIL- Not expecting any authentication key failure, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthFails().State()); got != 0 { + t.Errorf("FAIL- Not expecting any authentication key failure, got %d, want %d", got, 0) + } } // Checking authTypeFail counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthTypeFails().State()); got != 0 { - t.Errorf("FAIL- Not expecting any authentication type mismatches, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthTypeFails().State()); got != 0 { + t.Errorf("FAIL- Not expecting any authentication type mismatches, got %d, want %d", got, 0) + } } // Checking corrupted lsps counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { - t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { + t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) + } } // Checking database_overloads counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + } } // Checking execeeded maximum seq number counters"). - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) + } } // Checking IdLenMismatch counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().IdLenMismatch().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero IdLen_Mismatch counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().IdLenMismatch().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero IdLen_Mismatch counter, got %d, want %d", got, 0) + } } // Checking LspErrors counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().LspErrors().State()); got != 0 { - t.Errorf("FAIL- Not expecting any lsp errors, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().LspErrors().State()); got != 0 { + t.Errorf("FAIL- Not expecting any lsp errors, got %d, want %d", got, 0) + } } // Checking MaxAreaAddressMismatches counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().MaxAreaAddressMismatches().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero MaxAreaAddressMismatches counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().MaxAreaAddressMismatches().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero MaxAreaAddressMismatches counter, got %d, want %d", got, 0) + } } // Checking OwnLspPurges counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().OwnLspPurges().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero OwnLspPurges counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().OwnLspPurges().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero OwnLspPurges counter, got %d, want %d", got, 0) + } } // Checking SeqNumSkips counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) + } } // Checking ManualAddressDropFromAreas counters. - if !deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) { + if !(deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) || deviations.MissingValueForDefaults(ts.DUT)) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) } } // Checking PartChanges counters. - if !deviations.ISISCounterPartChangesUnsupported(ts.DUT) { + if !(deviations.ISISCounterPartChangesUnsupported(ts.DUT) || deviations.MissingValueForDefaults(ts.DUT)) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) } diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto b/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto index f14063d5dc4..75bc2932d0e 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto +++ b/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto @@ -10,6 +10,8 @@ platform_exceptions: { vendor: NOKIA } deviations: { + isis_global_authentication_not_required: true + isis_explicit_level_authentication_config: true isis_interface_level1_disable_required: true missing_isis_interface_afi_safi_enable: true explicit_port_speed: true diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md index 731ebc97f4c..d15347dad27 100644 --- a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md @@ -93,10 +93,10 @@ Tests that traceroute respects decap rules. 1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: ``` - IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { - {NH#1001, DEFAULT VRF, weight:1} + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#3001 (DEFAULT VRF) -> { + {NH#3001, DEFAULT VRF, weight:1} } - NH#1001 -> { + NH#3001 -> { decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 } @@ -146,10 +146,10 @@ Tests that traceroute for a packet with a route lookup miss has an unset target_ 1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: ``` - IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { - {NH#1001, DEFAULT VRF, weight:1} + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#3001 (DEFAULT VRF) -> { + {NH#3001, DEFAULT VRF, weight:1} } - NH#1001 -> { + NH#3001 -> { decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 } ``` @@ -238,8 +238,18 @@ Tests that traceroute for a packet with a route lookup miss has an unset target_ * network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance * network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance -## Protocol/RPC Parameter Coverage - -* gRIBI: - * Modify - * ModifyRequest +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Config parameter coverage diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto new file mode 100644 index 00000000000..b9ebd5de7e4 --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5a5491b5-9900-4a05-b289-43feb1ceb915" +plan_id: "P4RT-5.3" +description: "Traceroute: PacketIn With VRF Selection" +testbed: TESTBED_DUT_ATE_8LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + gnoi_subcomponent_path: true + deprecated_vlan_id: true + interface_enabled: true + static_protocol_name: "STATIC" + default_network_instance: "default" + gribi_mac_override_static_arp_static_route: true + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + ate_port_link_state_operations_unsupported: true + } +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go new file mode 100644 index 00000000000..d8e5c3e9739 --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go @@ -0,0 +1,275 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package to test P4RT with traceroute traffic of IPV4 and IPV6 with TTL/HopLimit as 0&1. +// go test -v . -testbed /root/ondatra/featureprofiles/topologies/atedut_2.testbed -binding /root/ondatra/featureprofiles/topologies/atedut_2.binding -outputs_dir logs + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ygnmi/ygnmi" + pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +type PacketIO interface { + GetTableEntry(delete bool, isIPv4 bool) []*p4rtutils.ACLWbbIngressTableEntryInfo + GetPacketTemplate() *PacketIOPacket + GetTrafficFlow(ate *ondatra.ATEDevice, dstMac string, isIpv4 bool, + TTL uint8, frameSize uint32, frameRate uint64, dstIP string, flowValues *flowArgs) gosnappi.Flow + GetIngressPort() string +} + +type PacketIOPacket struct { + TTL *uint8 + SrcMAC, DstMAC *string + EthernetType *uint32 + HopLimit *uint8 +} + +// programmTableEntry programs or deletes p4rt table entry based on delete flag. +func programmTableEntry(client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool, isIPv4 bool) error { + err := client.Write(&pb.WriteRequest{ + DeviceId: deviceID, + ElectionId: &pb.Uint128{High: uint64(0), Low: electionID}, + Updates: p4rtutils.ACLWbbIngressTableEntryGet( + packetIO.GetTableEntry(delete, isIPv4), + ), + Atomicity: pb.WriteRequest_CONTINUE_ON_ERROR, + }) + return err +} + +// decodePacket decodes L2 header in the packet and returns source and destination MAC and ethernet type. +func decodePacket(t *testing.T, packetData []byte) (string, string, layers.EthernetType) { + t.Helper() + packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.Default) + etherHeader := packet.Layer(layers.LayerTypeEthernet) + if etherHeader != nil { + header, decoded := etherHeader.(*layers.Ethernet) + if decoded { + return header.SrcMAC.String(), header.DstMAC.String(), header.EthernetType + } + } + return "", "", layers.EthernetType(0) +} + +// decodePacket decodes L2 header in the packet and returns TTL. packetData[14:0] to remove first 14 bytes of Ethernet header. +func decodePacket4(t *testing.T, packetData []byte) uint8 { + t.Helper() + packet := gopacket.NewPacket(packetData[14:], layers.LayerTypeIPv4, gopacket.Default) + if IPv4 := packet.Layer(layers.LayerTypeIPv4); IPv4 != nil { + ipv4, _ := IPv4.(*layers.IPv4) + IPv4 := ipv4.TTL + return IPv4 + } + return 7 +} + +// decodePacket decodes IPV6 L2 header in the packet and returns HopLimit. packetData[14:] to remove first 14 bytes of Ethernet header. +func decodePacket6(t *testing.T, packetData []byte) uint8 { + t.Helper() + packet := gopacket.NewPacket(packetData[14:], layers.LayerTypeIPv6, gopacket.Default) + if IPv6 := packet.Layer(layers.LayerTypeIPv6); IPv6 != nil { + ipv6, _ := IPv6.(*layers.IPv6) + IPv6 := ipv6.HopLimit + return IPv6 + } + return 7 +} + +// testTraffic sends traffic flow for duration seconds and returns the +// number of packets sent out. +func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flows []gosnappi.Flow, srcEndPoint gosnappi.Port, duration int, cs gosnappi.ControlState) int { + t.Helper() + top.Flows().Clear() + for _, flow := range flows { + flow.TxRx().Port().SetTxName(srcEndPoint.Name()).SetRxName(srcEndPoint.Name()) + flow.Metrics().SetEnable(true) + top.Flows().Append(flow) + } + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + ate.OTG().StartTraffic(t) + time.Sleep(time.Duration(duration) * time.Second) + ate.OTG().StopTraffic(t) + + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + ate.OTG().SetControlState(t, cs) + + outPkts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().FlowAny().Counters().OutPkts().State()) + total := 0 + for _, count := range outPkts { + total += int(count) + } + return total +} + +// testPacketIn programs p4rt table entry and sends traffic related to Traceroute, +// then validates packetin message metadata and payload. +func testPacketIn(ctx context.Context, t *testing.T, args *testArgs, isIPv4 bool, cs gosnappi.ControlState, flowValues []*flowArgs, EgressPortMap map[string]bool) []float64 { + leader := args.leader + if isIPv4 { + // Insert p4rtutils acl entry on the DUT + if err := programmTableEntry(leader, args.packetIO, false, isIPv4); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete p4rtutils acl entry on the device + defer programmTableEntry(leader, args.packetIO, true, isIPv4) + } else { + // Insert p4rtutils acl entry on the DUT + if err := programmTableEntry(leader, args.packetIO, false, false); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete p4rtutils acl entry on the device + defer programmTableEntry(leader, args.packetIO, true, false) + } + streamChan := args.leader.StreamChannelGet(&streamName) + qSize := 12000 + streamChan.SetArbQSize(qSize) + qSizeRead := streamChan.GetArbQSize() + if qSize != qSizeRead { + t.Errorf("Stream '%s' expecting Arbitration qSize(%d) Got (%d)", + streamName, qSize, qSizeRead) + } + + streamChan.SetPacketQSize(qSize) + qSizeRead = streamChan.GetPacketQSize() + if qSize != qSizeRead { + t.Errorf("Stream '%s' expecting Packet qSize(%d) Got (%d)", + streamName, qSize, qSizeRead) + } + + // Send Traceroute traffic from ATE + srcEndPoint := ateInterface(t, args.top, "port1") + llAddress, found := gnmi.Watch(t, args.ate.OTG(), gnmi.OTG().Interface("atePort1"+".Eth").Ipv4Neighbor(portsIPv4["dut:port1"]).LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + return val.IsPresent() + }).Await(t) + if !found { + t.Fatalf("Could not get the LinkLayerAddress %s", llAddress) + } + dstMac, _ := llAddress.Val() + var flow []gosnappi.Flow + for _, flowValue := range flowValues { + flow = append(flow, args.packetIO.GetTrafficFlow(args.ate, dstMac, isIPv4, 1, 300, 50, ipv4InnerDst, flowValue)) + } + pktOut := testTraffic(t, args.top, args.ate, flow, srcEndPoint, 60, cs) + var countPkts = map[string]int{"11": 0, "12": 0, "13": 0, "14": 0, "15": 0, "16": 0, "17": 0} + + packetInTests := []struct { + desc string + client *p4rt_client.P4RTClient + wantPkts int + }{{ + desc: "PacketIn to Primary Controller", + client: leader, + wantPkts: pktOut, + }} + + t.Log("TTL/HopLimit 1") + for _, test := range packetInTests { + t.Run(test.desc, func(t *testing.T) { + // Extract packets from PacketIn message sent to p4rt client + _, packets, err := test.client.StreamChannelGetPackets(&streamName, uint64(test.wantPkts), 30*time.Second) + if err != nil { + t.Errorf("Unexpected error on fetchPackets: %v", err) + } + + if test.wantPkts == 0 { + return + } + + gotPkts := 0 + t.Logf("Start to decode packet and compare with expected packets.") + wantPacket := args.packetIO.GetPacketTemplate() + + for _, packet := range packets { + if packet != nil { + srcMAC, _, etherType := decodePacket(t, packet.Pkt.GetPayload()) + if etherType != layers.EthernetTypeIPv4 && etherType != layers.EthernetTypeIPv6 { + continue + } + if !strings.EqualFold(srcMAC, tracerouteSrcMAC) { + continue + } + if wantPacket.TTL != nil { + // TTL/HopLimit comparison for IPV4 & IPV6 + if isIPv4 { + captureTTL := decodePacket4(t, packet.Pkt.GetPayload()) + if captureTTL != TTL1 { + t.Fatalf("Packet in PacketIn message is not matching wanted packet=IPV4 TTL1") + } + + } else { + captureHopLimit := decodePacket6(t, packet.Pkt.GetPayload()) + if captureHopLimit != HopLimit1 { + t.Fatalf("Packet in PacketIn message is not matching wanted packet=IPV6 HopLimit1") + } + } + } + + // Metadata comparision + if metaData := packet.Pkt.GetMetadata(); metaData != nil { + if got := metaData[0].GetMetadataId(); got == MetadataIngressPort { + if gotPortID := string(metaData[0].GetValue()); gotPortID != args.packetIO.GetIngressPort() { + t.Fatalf("Ingress Port Id mismatch: want %s, got %s", args.packetIO.GetIngressPort(), gotPortID) + } + } else { + t.Fatalf("Metadata ingress port mismatch: want %d, got %d", MetadataIngressPort, got) + } + if got := metaData[1].GetMetadataId(); got == MetadataEgressPort { + countPkts[string(metaData[1].GetValue())]++ + if gotPortID := string(metaData[1].GetValue()); !EgressPortMap[gotPortID] { + t.Fatalf("Egress Port Id mismatch: got %s", gotPortID) + } + } else { + t.Fatalf("Metadata egress port mismatch: want %d, got %d", MetadataEgressPort, got) + } + } else { + t.Fatalf("Packet missing metadata information.") + } + gotPkts++ + } + } + if got, want := gotPkts, test.wantPkts; got != want { + t.Errorf("Number of PacketIn, got: %d, want: %d", got, want) + } + }) + } + loadBalancePercent := []float64{float64(countPkts["11"]) / float64(pktOut), float64(countPkts["12"]) / float64(pktOut), + float64(countPkts["13"]) / float64(pktOut), float64(countPkts["14"]) / float64(pktOut), float64(countPkts["15"]) / float64(pktOut), + float64(countPkts["16"]) / float64(pktOut), float64(countPkts["17"]) / float64(pktOut)} + + return loadBalancePercent +} + +func ateInterface(t *testing.T, topo gosnappi.Config, portID string) gosnappi.Port { + for _, p := range topo.Ports().Items() { + if p.Name() == portID { + return p + } + } + return nil +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go new file mode 100644 index 00000000000..d3e831d795a --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go @@ -0,0 +1,264 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package to test P4RT with traceroute traffic of IPV4 and IPV6 with TTL/HopLimit as 0&1. +// go test -v . -testbed /root/ondatra/featureprofiles/topologies/atedut_2.testbed -binding /root/ondatra/featureprofiles/topologies/atedut_2.binding -outputs_dir logs + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/cisco-open/go-p4/utils" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" + pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +// configureDeviceIDs configures p4rt device-id on the DUT. +func configureDeviceID(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice) { + nodes := p4rtutils.P4RTNodesByPort(t, dut) + p4rtNode, ok := nodes["port1"] + if !ok { + t.Fatal("Couldn't find P4RT Node for port: port1") + } + t.Logf("Configuring P4RT Node: %s", p4rtNode) + c := oc.Component{} + c.Name = ygot.String(p4rtNode) + c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} + c.IntegratedCircuit.NodeId = ygot.Uint64(deviceID) + gnmi.Replace(t, dut, gnmi.OC().Component(p4rtNode).Config(), &c) +} + +// setupP4RTClient sends client arbitration message for both leader and follower clients, +// then sends setforwordingpipelineconfig with leader client. +func setupP4RTClient(ctx context.Context, args *testArgs) error { + // Setup p4rt-client stream parameters + streamParameter := p4rt_client.P4RTStreamParameters{ + Name: streamName, + DeviceId: deviceID, + ElectionIdH: uint64(0), + ElectionIdL: electionID, + } + + // Send ClientArbitration message on both p4rt leader and follower clients. + clients := []*p4rt_client.P4RTClient{args.leader, args.follower} + for index, client := range clients { + if client != nil { + client.StreamChannelCreate(&streamParameter) + if err := client.StreamChannelSendMsg(&streamName, &pb.StreamMessageRequest{ + Update: &pb.StreamMessageRequest_Arbitration{ + Arbitration: &pb.MasterArbitrationUpdate{ + DeviceId: streamParameter.DeviceId, + ElectionId: &pb.Uint128{ + High: streamParameter.ElectionIdH, + Low: streamParameter.ElectionIdL - uint64(index), + }, + }, + }, + }); err != nil { + return fmt.Errorf("errors seen when sending ClientArbitration message: %v", err) + } + if _, _, arbErr := client.StreamChannelGetArbitrationResp(&streamName, 1); arbErr != nil { + if err := p4rtutils.StreamTermErr(client.StreamTermErr); err != nil { + return err + } + return fmt.Errorf("errors seen in ClientArbitration response: %v", arbErr) + } + } + } + + // Load p4info file. + p4Info, err := utils.P4InfoLoad(p4InfoFile) + if err != nil { + return errors.New("errors seen when loading p4info file") + } + + // Send SetForwardingPipelineConfig for p4rt leader client. + if err := args.leader.SetForwardingPipelineConfig(&pb.SetForwardingPipelineConfigRequest{ + DeviceId: deviceID, + ElectionId: &pb.Uint128{High: uint64(0), Low: electionID}, + Action: pb.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &pb.ForwardingPipelineConfig{ + P4Info: p4Info, + Cookie: &pb.ForwardingPipelineConfig_Cookie{ + Cookie: 159, + }, + }, + }); err != nil { + return errors.New("errors seen when sending SetForwardingPipelineConfig") + } + return nil +} + +// getTracerouteParameter returns Traceroute related parameters for testPacketIn testcase. +func getTracerouteParameter(t *testing.T) PacketIO { + return &TraceroutePacketIO{ + PacketIOPacket: PacketIOPacket{ + TTL: &TTL1, + HopLimit: &HopLimit1, + }, + IngressPort: fmt.Sprint(portID), + EgressPort: fmt.Sprint(portID + 7), + } +} + +// testPacket setup p4RT client, table entry and send traffic and returns the +// percentage of packets sent out of each egress port +func testPacket(t *testing.T, args *testArgs, cs gosnappi.ControlState, flowValues []*flowArgs, EgressPortMap map[string]bool) []float64 { + dut := ondatra.DUT(t, "dut") + ctx := context.Background() + ate := ondatra.ATE(t, "ate") + top := args.otgConfig + top.Flows().Clear() + + configureDeviceID(ctx, t, dut) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + + args = &testArgs{ + ctx: ctx, + leader: args.leader, + follower: args.follower, + dut: dut, + ate: ate, + top: top, + } + + if err := setupP4RTClient(ctx, args); err != nil { + t.Fatalf("Could not setup p4rt client: %v", err) + } + args.packetIO = getTracerouteParameter(t) + return testPacketIn(ctx, t, args, true, cs, flowValues, EgressPortMap) +} + +type TraceroutePacketIO struct { + PacketIOPacket + IngressPort string + EgressPort string +} + +// GetTableEntry creates p4rtutils acl entry which is used to get the configured p4rt trap rules. +// A packet is sent to controller based on the trap rules +func (traceroute *TraceroutePacketIO) GetTableEntry(delete bool, IsIpv4 bool) []*p4rtutils.ACLWbbIngressTableEntryInfo { + if IsIpv4 { + actionType := pb.Update_INSERT + if delete { + actionType = pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + IsIpv4: 0x1, + TTL: 0x1, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: actionType, + IsIpv4: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }} + } + actionType := pb.Update_INSERT + if delete { + actionType = pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + IsIpv6: 0x1, + TTL: 0x1, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: actionType, + IsIpv6: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }} +} + +// GetPacketTemplate returns expected packets in PacketIn. +func (traceroute *TraceroutePacketIO) GetPacketTemplate() *PacketIOPacket { + return &traceroute.PacketIOPacket +} + +func (traceroute *TraceroutePacketIO) GetTrafficFlow(ate *ondatra.ATEDevice, dstMac string, isIpv4 bool, + TTL uint8, frameSize uint32, frameRate uint64, dstIP string, flowValues *flowArgs) gosnappi.Flow { + + flow := gosnappi.NewFlow() + flow.SetName(flowValues.flowName) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(tracerouteSrcMAC) + ethHeader.Dst().SetValue(dstMac) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(flowValues.outHdrSrcIP) + ipHeader.Dst().SetValue(flowValues.outHdrDstIP) + ipHeader.TimeToLive().SetValue(uint32(TTL)) + if len(flowValues.outHdrDscp) != 0 { + ipHeader.Priority().Dscp().Phb().SetValues(flowValues.outHdrDscp) + } + if flowValues.udp { + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + + if flowValues.proto != 0 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Protocol().SetValue(flowValues.proto) + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + innerIPHdr.TimeToLive().SetValue(uint32(TTL)) + } else { + if flowValues.isInnHdrV4 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } else { + innerIpv6Hdr := flow.Packet().Add().Ipv6() + innerIpv6Hdr.Src().SetValue(flowValues.InnHdrSrcIPv6) + innerIpv6Hdr.Dst().SetValue(flowValues.InnHdrDstIPv6) + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + } + + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(uint64(frameRate)) + return flow +} + +// GetIngressPort return expected ingress port info in Packetin. +func (traceroute *TraceroutePacketIO) GetIngressPort() string { + return traceroute.IngressPort +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go new file mode 100644 index 00000000000..1d757dab977 --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go @@ -0,0 +1,1141 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "flag" + "fmt" + "sort" + "strconv" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + baseScenario "github.com/openconfig/featureprofiles/internal/encapfrr" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +// Settings for configuring the baseline testbed with the test +// topology. +// +// ATE port-1 <------> port-1 DUT +// DUT port-2 <------> port-2 ATE +// DUT port-3 <------> port-3 ATE +// DUT port-4 <------> port-4 ATE +// DUT port-5 <------> port-5 ATE +// DUT port-6 <------> port-6 ATE +// DUT port-7 <------> port-7 ATE +// DUT port-8 <------> port-8 ATE + +const ( + plenIPv4 = 30 + plenIPv6 = 126 + dscpEncapA1 = 10 + dscpEncapB1 = 20 + dscpEncapNoMatch = 30 + ipv4OuterSrc111Addr = "198.51.100.111" + ipv4OuterSrc222Addr = "198.51.100.222" + ipv4OuterSrcAddr = "198.100.200.123" + ipv4OuterDst111 = "192.51.100.64" + ipv4OuterDst111WithMask = "192.51.100.0/24" + ipv4InnerDst = "138.0.11.8" + noMatchEncapDest = "20.0.0.1" + maskLen24 = "24" + maskLen126 = "124" + maskLen32 = "32" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niTeVrf111 = "TE_VRF_111" + niTeVrf222 = "TE_VRF_222" + niRepairVrf = "REPAIR_VRF" + niTransitVRF = "TRANSIT_VRF" + tolerance = 0.02 + encapFlow = "encapFlow" + gribiIPv4EntryVRF1111 = "203.0.113.1" + gribiIPv4EntryVRF1112 = "203.0.113.2" + gribiIPv4EntryEncapVRF = "138.0.11.0" + gribiIPv6EntryEncapVRF = "2001:db8::138:0:11:0" + ipv6InnerDst = "2001:db8::138:0:11:8" + ipv6InnerDstNoEncap = "2001:db8::20:0:0:1" + + dutAreaAddress = "49.0001" + dutSysID = "1920.0000.2001" + otgSysID1 = "640000000001" + isisInstance = "DEFAULT" + otgIsisPort8LoopV4 = "203.0.113.10" + otgIsisPort8LoopV6 = "2001:db8::203:0:113:10" + dutAS = 65501 + peerGrpName1 = "BGP-PEER-GROUP1" + ipOverIPProtocol = 4 +) + +var ( + p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + streamName = "p4rt" + tracerouteSrcMAC = "00:01:00:02:00:03" + deviceID = uint64(1) + portID = uint32(10) + electionID = uint64(100) + MetadataIngressPort = uint32(1) + MetadataEgressPort = uint32(2) + TTL1 = uint8(1) + HopLimit1 = uint8(1) + + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + MAC: "02:00:01:01:01:01", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + MAC: "02:00:01:01:01:02", + IPv6: "2001:db8::192:0:2:6", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv6: "2001:db8::192:0:2:9", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort3 = attrs.Attributes{ + Name: "atePort3", + IPv4: "192.0.2.10", + MAC: "02:00:01:01:01:03", + IPv6: "2001:db8::192:0:2:a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv6: "2001:db8::192:0:2:d", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort4 = attrs.Attributes{ + Name: "atePort4", + IPv4: "192.0.2.14", + MAC: "02:00:01:01:01:04", + IPv6: "2001:db8::192:0:2:e", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort5 = attrs.Attributes{ + Desc: "dutPort5", + IPv4: "192.0.2.17", + IPv6: "2001:db8::192:0:2:11", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort5 = attrs.Attributes{ + Name: "atePort5", + IPv4: "192.0.2.18", + MAC: "02:00:01:01:01:05", + IPv6: "2001:db8::192:0:2:12", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort6 = attrs.Attributes{ + Desc: "dutPort6", + IPv4: "192.0.2.21", + IPv6: "2001:db8::192:0:2:15", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort6 = attrs.Attributes{ + Name: "atePort6", + IPv4: "192.0.2.22", + MAC: "02:00:01:01:01:06", + IPv6: "2001:db8::192:0:2:16", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort7 = attrs.Attributes{ + Desc: "dutPort7", + IPv4: "192.0.2.25", + IPv6: "2001:db8::192:0:2:19", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort7 = attrs.Attributes{ + Name: "atePort7", + IPv4: "192.0.2.26", + MAC: "02:00:01:01:01:07", + IPv6: "2001:db8::192:0:2:1a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort8 = attrs.Attributes{ + Desc: "dutPort8", + IPv4: "192.0.2.29", + IPv6: "2001:db8::192:0:2:1d", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort8 = attrs.Attributes{ + Name: "atePort8", + IPv4: "192.0.2.30", + MAC: "02:00:01:01:01:08", + IPv6: "2001:db8::192:0:2:1e", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + // loopbackIntfName string + // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 + // Below code will be uncommented once ixia issue is fixed. + // tolerance = 0.2 + + portsMap = map[string]attrs.Attributes{ + "dutPort1": dutPort1, + "atePort1": atePort1, + "dutPort2": dutPort2, + "atePort2": atePort2, + "dutPort3": dutPort3, + "atePort3": atePort3, + "dutPort4": dutPort4, + "atePort4": atePort4, + "dutPort5": dutPort5, + "atePort5": atePort5, + "dutPort6": dutPort6, + "atePort6": atePort6, + "dutPort7": dutPort7, + "atePort7": atePort7, + "dutPort8": dutPort8, + "atePort8": atePort8, + } + portsIPv4 = map[string]string{ + "dut:port1": "192.0.2.1", + "ate:port1": "192.0.2.2", + + "dut:port2": "192.0.2.5", + "ate:port2": "192.0.2.6", + + "dut:port3": "192.0.2.9", + "ate:port3": "192.0.2.10", + + "dut:port4": "192.0.2.13", + "ate:port4": "192.0.2.14", + + "dut:port5": "192.0.2.17", + "ate:port5": "192.0.2.18", + + "dut:port6": "192.0.2.21", + "ate:port6": "192.0.2.22", + + "dut:port7": "192.0.2.25", + "ate:port7": "192.0.2.26", + + "dut:port8": "192.0.2.29", + "ate:port8": "192.0.2.30", + } + portsIPv6 = map[string]string{ + "dut:port1": "2001:db8::192:0:2:1", + "ate:port1": "2001:db8::192:0:2:2", + + "dut:port2": "2001:db8::192:0:2:5", + "ate:port2": "2001:db8::192:0:2:6", + + "dut:port3": "2001:db8::192:0:2:9", + "ate:port3": "2001:db8::192:0:2:a", + + "dut:port4": "2001:db8::192:0:2:d", + "ate:port4": "2001:db8::192:0:2:e", + + "dut:port5": "2001:db8::192:0:2:11", + "ate:port5": "2001:db8::192:0:2:12", + + "dut:port6": "2001:db8::192:0:2:15", + "ate:port6": "2001:db8::192:0:2:16", + + "dut:port7": "2001:db8::192:0:2:19", + "ate:port7": "2001:db8::192:0:2:1a", + + "dut:port8": "2001:db8::192:0:2:1d", + "ate:port8": "2001:db8::192:0:2:1e", + } + otgPortDevices []gosnappi.Device + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.11", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + loopbackIntfName string + atePortNamelist []string +) + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +type testArgs struct { + ctx context.Context + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + top gosnappi.Config + electionID gribi.Uint128 + otg *otg.OTG + leader *p4rt_client.P4RTClient + follower *p4rt_client.P4RTClient + packetIO PacketIO +} + +type flowArgs struct { + flowName string + outHdrSrcIP, outHdrDstIP string + InnHdrSrcIP, InnHdrDstIP string + InnHdrSrcIPv6, InnHdrDstIPv6 string + udp, isInnHdrV4 bool + outHdrDscp []uint32 + proto uint32 +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func sortPorts(ports []*ondatra.Port) []*ondatra.Port { + sort.Slice(ports, func(i, j int) bool { + idi, idj := ports[i].ID(), ports[j].ID() + li, lj := len(idi), len(idj) + if li == lj { + return idi < idj + } + return li < lj // "port2" < "port10" + }) + return ports +} + +// dutInterface builds a DUT interface ygot struct for a given port +// according to portsIPv4. Returns nil if the port has no IP address +// mapping. +func dutInterface(p *ondatra.Port, dut *ondatra.DUTDevice, portIDx uint32) *oc.Interface { + id := fmt.Sprintf("%s:%s", p.Device().ID(), p.ID()) + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Description: ygot.String(p.String()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Id: ygot.Uint32(portIDx), + } + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + if p.PMD() == ondatra.PMD100GBASEFR { + e := i.GetOrCreateEthernet() + e.AutoNegotiate = ygot.Bool(false) + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } + + ipv4, ok := portsIPv4[id] + if !ok { + return nil + } + ipv6, ok := portsIPv6[id] + if !ok { + return nil + } + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + + a := s4.GetOrCreateAddress(ipv4) + a.PrefixLength = ygot.Uint8(plenIPv4) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(ipv6) + a6.PrefixLength = ygot.Uint8(plenIPv6) + + return i +} + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice, dutPortList []*ondatra.Port) { + dc := gnmi.OC() + for idx, dp := range dutPortList { + portIDx := portID + uint32(idx) + if i := dutInterface(dp, dut, portIDx); i != nil { + gnmi.Replace(t, dut, dc.Interface(dp.Name()).Config(), i) + } else { + t.Fatalf("No address found for port %v", dp) + } + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, dp := range dut.Ports() { + fptest.AssignToNetworkInstance(t, dut, dp.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } + if deviations.ExplicitPortSpeed(dut) { + for _, dp := range dut.Ports() { + fptest.SetPortSpeed(t, dp) + } + } + + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loop1) + } else { + v4, ok := ipv4Addrs[0].Val() + if ok { + dutlo0Attrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutlo0Attrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutlo0Attrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) + } +} + +// configureAdditionalGribiAft configures additional AFT entries for Gribi. +func configureAdditionalGribiAft(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3000).WithNextHopNetworkInstance(niRepairVrf), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3000).AddNextHop(3000, 1), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1000), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(1001), + + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(101). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range defaultVRFIPList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(200).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(200).AddNextHop(200, 1), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + args.client.Modify().AddEntry(t, + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(102).AddNextHop(101, 3).AddNextHop(102, 1).WithBackupNHG(200), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv6Operation(gribiIPv6EntryEncapVRF+"/124"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + +} + +// configureGribiRoute configures Gribi route for prefix +func configureGribiRoute(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs, prefWithMask string) { + t.Helper() + // Using gRIBI, install an IPv4Entry for the prefix 192.51.100.1/24 that points to a + // NextHopGroup that contains a single NextHop that specifies decapsulating the IPv4 + // header and specifies the DEFAULT network instance.This IPv4Entry should be installed + // into the DECAP_TE_VRF. + + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3001).WithDecapsulateHeader(fluent.IPinIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3001).AddNextHop(3001, 1), + fluent.IPv4Entry().WithNetworkInstance(niDecapTeVrf). + WithPrefix(prefWithMask).WithNextHopGroup(3001). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation(prefWithMask).WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) +} + +// configureISIS configures ISIS on the DUT. +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + + lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() + lspBit.SetBit = ygot.Bool(false) + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(intfName) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + + if deviations.InterfaceRefConfigUnsupported(dut) { + isisIntf.InterfaceRef = nil + } + + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Metric = ygot.Uint32(20) + isisIntfLevelAfiv6.Metric = ygot.Uint32(20) + + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + gnmi.Update(t, dut, gnmi.OC().Config(), d) +} + +// bgpCreateNbr creates BGP neighbor configuration +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(otgIsisPort8LoopV4) + bgpNbr.PeerGroup = ygot.String(peerGrpName1) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + bgpNbrT.LocalAddress = ygot.String(dutlo0Attrs.IPv4) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +// verifyISISTelemetry verifies ISIS telemetry. +func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIntf = dutIntf + ".0" + } + nbrPath := statePath.Interface(dutIntf) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } +} + +// verifyBgpTelemetry verifies BGP telemetry. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(otgIsisPort8LoopV4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", otgIsisPort8LoopV4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", otgIsisPort8LoopV4, state, want) + } +} + +// configureOTG configures the topology of the ATE. +func configureOTG(t testing.TB, otg *otg.OTG, atePorts []*ondatra.Port) gosnappi.Config { + t.Helper() + t.Logf("configureOTG") + config := gosnappi.NewConfig() + pmd100GFRPorts := []string{} + for i, ap := range atePorts { + if ap.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, ap.ID()) + } + // DUT and ATE ports are connected by the same names. + dutid := fmt.Sprintf("dut:%s", ap.ID()) + ateid := fmt.Sprintf("ate:%s", ap.ID()) + + port := config.Ports().Add().SetName(ap.ID()) + atePortNamelist = append(atePortNamelist, port.Name()) + portName := fmt.Sprintf("atePort%s", strconv.Itoa(i+1)) + dev := config.Devices().Add().SetName(portName) + macAddress := portsMap[portName].MAC + eth := dev.Ethernets().Add().SetName(portName + ".Eth").SetMac(macAddress) + eth.Connection().SetPortName(port.Name()) + eth.Ipv4Addresses().Add().SetName(portName + ".IPv4"). + SetAddress(portsIPv4[ateid]).SetGateway(portsIPv4[dutid]). + SetPrefix(plenIPv4) + eth.Ipv6Addresses().Add().SetName(portName + ".IPv6"). + SetAddress(portsIPv6[ateid]).SetGateway(portsIPv6[dutid]). + SetPrefix(plenIPv6) + + otgPortDevices = append(otgPortDevices, dev) + if i == 7 { + iDut8LoopV4 := dev.Ipv4Loopbacks().Add().SetName("Port8LoopV4").SetEthName(eth.Name()) + iDut8LoopV4.SetAddress(otgIsisPort8LoopV4) + iDut8LoopV6 := dev.Ipv6Loopbacks().Add().SetName("Port8LoopV6").SetEthName(eth.Name()) + iDut8LoopV6.SetAddress(otgIsisPort8LoopV6) + isisDut := dev.Isis().SetName("ISIS1").SetSystemId(otgSysID1) + isisDut.Basic().SetIpv4TeRouterId(portsIPv4[ateid]).SetHostname(isisDut.Name()).SetLearnedLspFilter(true) + isisDut.Interfaces().Add().SetEthName(dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt1"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port8 loopback address via ISIS. + isisPort2V4 := dev.Isis().V4Routes().Add().SetName("ISISPort8V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort8LoopV4).SetPrefix(32) + isisPort2V6 := dev.Isis().V6Routes().Add().SetName("ISISPort8V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort8LoopV6).SetPrefix(uint32(128)) + iDutBgp := dev.Bgp().SetRouterId(otgIsisPort8LoopV4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(iDut8LoopV4.Name()).Peers().Add().SetName(ap.ID() + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4Unicast(true).SetIpv6Unicast(true) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(port.Name() + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(noMatchEncapDest).SetPrefix(32). + SetCount(1).SetStep(1) + + bgpNeti1Bgp6PeerRoutes := iDutBgp4Peer.V6Routes().Add().SetName(atePort8.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(otgIsisPort8LoopV6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDst).SetPrefix(128). + SetCount(1).SetStep(1) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDstNoEncap).SetPrefix(128). + SetCount(1).SetStep(1) + + } + + } + config.Captures().Add().SetName("packetCapture"). + SetPortNames([]string{atePortNamelist[1], atePortNamelist[2], atePortNamelist[3], atePortNamelist[4], + atePortNamelist[5], atePortNamelist[6], atePortNamelist[7]}). + SetFormat(gosnappi.CaptureFormat.PCAP) + + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + pb, _ := config.Marshal().ToProto() + t.Log(pb.GetCaptures()) + return config +} + +func startCapture(t *testing.T, args *testArgs, capturePortList []string) gosnappi.ControlState { + t.Helper() + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(capturePortList). + SetFormat(gosnappi.CaptureFormat.PCAP) + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + return cs +} + +func verifyPortStatus(t *testing.T, args *testArgs, portList []string, portStatus bool) { + wantStatus := oc.Interface_OperStatus_UP + if !portStatus { + wantStatus = oc.Interface_OperStatus_DOWN + } + for _, port := range portList { + p := args.dut.Port(t, port) + t.Log("Check for port status") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), 1*time.Minute, wantStatus) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if operStatus != wantStatus { + t.Errorf("Get(DUT %v oper status): got %v, want %v", port, operStatus, wantStatus) + } + } +} + +func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64, gotWeights []float64) { + t.Log("Verify packet load balancing as per the programmed weight") + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} + +// testGribiMatchNoSourceNoProtocolMacthDSCP is to test based on packet which doesn't match source IP and protocol +// but match DSCP value +// Test-1 +func testGribiMatchNoSourceNoProtocolMacthDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": true, "16": false, "17": false} + LoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrcAddr, outHdrDstIP: ipv4InnerDst, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTunnelTrafficMatchDefaultTerm is to test Tunnel traffic match default term +// Test-2 +func testTunnelTrafficMatchDefaultTerm(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": false, "12": false, "13": false, "14": false, "15": false, "16": false, "17": true} + LoadBalancePercent := []float64{0, 0, 0, 0, 0, 0, 1} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrcAddr, outHdrDstIP: noMatchEncapDest, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: noMatchEncapDest, isInnHdrV4: true, udp: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testGribiDecapMatchSrcProtoDSCP is to test Gribi decap match src proto DSCP +// Test-3 +func testGribiDecapMatchSrcProtoDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": false, "16": false, "17": false} + LoadBalancePercent := []float64{0.0625, 0.1875, 0.75, 0, 0, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: gribiIPv4EntryVRF1111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTunnelTrafficNoDecap is to test Tunnel traffic no decap +// Test-6 +func testTunnelTrafficNoDecap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": false, "12": false, "13": false, "14": false, "15": false, "16": false, "17": true} + LoadBalancePercent := []float64{0, 0, 0, 0, 0, 0, 1} + + cases := []struct { + desc string + prefixWithMask string + }{{ + desc: "Mask Length 24", + prefixWithMask: "192.51.100.0/24", + }, { + desc: "Mask Length 32", + prefixWithMask: "192.51.100.64/32", + }, { + desc: "Mask Length 28", + prefixWithMask: "192.51.100.64/28", + }, { + desc: "Mask Length 22", + prefixWithMask: "192.51.100.0/22", + }} + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + t.Run("Program gRIBi route for Prefix "+tc.prefixWithMask, func(t *testing.T) { + configureGribiRoute(ctx, t, dut, args, tc.prefixWithMask) + }) + t.Run("Create ip-in-ip and ipv6-in-ip flows, send traffic and verify decap functionality", + func(t *testing.T) { + // Send both 6in4 and 4in4 packets. Verify that the packets have their outer + // v4 header stripped and are forwarded according to the route in the DEFAULT + // VRF that matches the inner IP address. + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) + }) + }) + } +} + +// testTunnelTrafficDecapEncap is to test Tunnel traffic decap encap +// Test-9 +func testTunnelTrafficDecapEncap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + configureGribiRoute(ctx, t, dut, args, ipv4OuterDst111WithMask) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": true, "16": false, "17": false} + LoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc222Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) + + LoadBalancePercent = []float64{0.0468, 0.1406, 0.5625, 0, 0.25, 0, 0} + flow = []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc222Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState = startCapture(t, args, baseCapturePortList) + gotWeights = testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTraceRoute is to test Test FRR behaviors with encapsulation scenarios +func TestTraceRoute(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + gribic := dut.RawAPIs().GRIBI(t) + top := gosnappi.NewConfig() + dutPorts := sortPorts(dut.Ports())[0:8] + atePorts := sortPorts(ate.Ports())[0:8] + + t.Log("Configure Default Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy("match-ipip") + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(deviations.DefaultNetworkInstance(dut)) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String("match-ipip") + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) + } + + t.Run("Configure Interface on DUT", func(t *testing.T) { + configureDUT(t, dut, dutPorts) + }) + + t.Log("Apply vrf selection policy_c to DUT port-1") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyC) + + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + // staticARPWithMagicUniversalIP(t, dut) + baseScenario.StaticARPWithMagicUniversalIP(t, dut) + } + + t.Log("Install BGP route resolved by ISIS.") + t.Log("Configure ISIS on DUT") + configureISIS(t, dut, dut.Port(t, "port8").Name(), dutAreaAddress, dutSysID) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + var otgConfig gosnappi.Config + t.Run("Configure OTG", func(t *testing.T) { + otgConfig = configureOTG(t, otg, atePorts) + }) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + client.Start(ctx, t) + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for clientA: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + leader := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := leader.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + t.Fatalf("Could not initialize p4rt client: %v", err) + } + + follower := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := follower.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + t.Fatalf("Could not initialize p4rt client: %v", err) + } + args := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + ate: ate, + otgConfig: otgConfig, + top: top, + electionID: eID, + otg: otg, + leader: leader, + follower: follower, + } + t.Log("Configure gRIBI routes") + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(client); err != nil { + t.Fatal(err) + } + + t.Log("Verify whether the ports are in up state") + portList := []string{"port2", "port3", "port4", "port5", "port6", "port7", "port8"} + verifyPortStatus(t, args, portList, true) + + t.Log("Verify ISIS telemetry") + verifyISISTelemetry(t, dut, dut.Port(t, "port8").Name()) + t.Log("Verify BGP telemetry") + verifyBgpTelemetry(t, dut) + + t.Run("Test-1: Match on DSCP, no Source and no Protocol", func(t *testing.T) { + testGribiMatchNoSourceNoProtocolMacthDSCP(ctx, t, dut, args) + }) + + t.Log("Delete vrf selection policy C and Apply vrf selectioin policy W") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + + t.Run("Test-2: Match on default term and send to default VRF", func(t *testing.T) { + testTunnelTrafficMatchDefaultTerm(ctx, t, dut, args) + }) + t.Run("Test-3: Match on source, protocol and DSCP, VRF_DECAP hit -> VRF_ENCAP_A miss -> DEFAULT", func(t *testing.T) { + testGribiDecapMatchSrcProtoDSCP(ctx, t, dut, args) + }) + // Below test case will implement later + /* + t.Run("Test-4: Tests that traceroute respects transit FRR", func(t *testing.T) { + + }) + t.Run("Test-5: Tests that traceroute respects transit FRR when the backup is also unviable.", func(t *testing.T) { + + })*/ + t.Run("Test-6: Tunneled traffic with no decap", func(t *testing.T) { + testTunnelTrafficNoDecap(ctx, t, dut, args) + }) + // Below test case will implement later + /* + t.Run("Test-7: Encap failure cases (TBD on confirmation)", func(t *testing.T) { + + }) + t.Run("Test-8: Tests that traceroute for a packet with a route lookup miss has an unset target_egress_port.", func(t *testing.T) { + + })*/ + t.Run("Test-9: Decap then encap", func(t *testing.T) { + testTunnelTrafficDecapEncap(ctx, t, dut, args) + }) +} diff --git a/feature/experimental/policy/otg_tests/prefix_set_test/README.md b/feature/experimental/policy/otg_tests/prefix_set_test/README.md index 59789d7cd6f..20ccedcaaa8 100644 --- a/feature/experimental/policy/otg_tests/prefix_set_test/README.md +++ b/feature/experimental/policy/otg_tests/prefix_set_test/README.md @@ -2,7 +2,9 @@ ## Summary -- Prefix list is updated and replaced correctly +- Prefix list is updated and replaced correctly after restarting the process + with supports gNOI to validate that internal state of OC agent is in sync + with the running configuration. ## Testbed type @@ -67,23 +69,23 @@ the DUT in one gnmi.Set using the `replace` option ### RT-1.53.4 [TODO:https://github.com/openconfig/featureprofiles/issues/3306] -### Add prefix-list from cli and then replace with gnmi +### Create prefix list and replace with gnmi. -* Configure Prefix-list through CLI Config Sessions. +* Send a gNMI SET request that contains below prefixes under TAG_3_IPV4 prefix-set ``` - ip prefix-list TAG_3_IPV4 - seq 10 permit 10.240.31.48/28 - seq 20 permit 10.244.187.32/28 - seq 30 permit 173.36.128.0/20 - seq 40 permit 173.37.128.0/20 - seq 50 permit 173.38.128.0/20 - seq 60 permit 173.39.128.0/20 - seq 70 permit 173.40.128.0/20 - seq 80 permit 173.41.128.0/20 - seq 90 permit 173.42.128.0/20 - seq 100 permit 173.43.128.0/20 + 10.240.31.48/28 + 10.244.187.32/28 + 173.36.128.0/20 + 173.37.128.0/20 + 173.38.128.0/20 + 173.39.128.0/20 + 173.40.128.0/20 + 173.41.128.0/20 + 173.42.128.0/20 + 173.43.128.0/20 ``` -* Perform octa restart or reboot the device. +* Validate that the prefix-list is created correctly with 10 prefixes. +* Use gNOI to kill the process supporting gNMI. * Send a gNMI SET request that contains additional prefixes within the same prefix-set, TAG_3_IPV4. ``` @@ -110,6 +112,7 @@ the DUT in one gnmi.Set using the `replace` option 173.48.128.0/20 173.45.128.0/20 ``` +* Validate that the prefix-list is created correctly with 22 prefixes. ## OpenConfig Path and RPC Coverage @@ -136,6 +139,8 @@ rpcs: gnmi: gNMI.Set: gNMI.Subscribe: + gnoi: + system.System.KillProcess: ``` ## Required DUT platform diff --git a/feature/experimental/policy/otg_tests/prefix_set_test/prefix_set_test.go b/feature/experimental/policy/otg_tests/prefix_set_test/prefix_set_test.go index 7dab71b0d34..d6196b434b7 100644 --- a/feature/experimental/policy/otg_tests/prefix_set_test/prefix_set_test.go +++ b/feature/experimental/policy/otg_tests/prefix_set_test/prefix_set_test.go @@ -15,12 +15,11 @@ package prefix_set_test import ( - "context" "testing" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" - "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/featureprofiles/internal/gnoi" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -28,6 +27,7 @@ import ( const ( prefixSetA = "PFX_SET_A" + tag3IPv4 = "TAG_3_IPV4" pfx1 = "10.240.31.48/28" pfx2 = "173.36.128.0/20" pfx3 = "173.36.144.0/20" @@ -107,48 +107,38 @@ func TestPrefixSet(t *testing.T) { } } -func TestPrefixSetWithCLIConfig(t *testing.T) { +func TestPrefixSetWithOCAgentRestart(t *testing.T) { dut := ondatra.DUT(t, "dut") - ctx := context.Background() - cli := dut.RawAPIs().CLI(t) - - switch dut.Vendor() { - case ondatra.ARISTA: - helpers.GnmiCLIConfig(t, dut, ` - ip prefix-list TAG_3_IPV4 - seq 10 permit 10.240.31.48/28 - seq 20 permit 10.244.187.32/28 - seq 30 permit 173.36.128.0/20 - seq 40 permit 173.37.128.0/20 - seq 50 permit 173.38.128.0/20 - seq 60 permit 173.39.128.0/20 - seq 70 permit 173.40.128.0/20 - seq 80 permit 173.41.128.0/20 - seq 90 permit 173.42.128.0/20 - seq 100 permit 173.43.128.0/20 - `, - ) - helpers.GnmiCLIConfig(t, dut, ` - management api gnmi - transport grpc default - operation set persistence - `, - ) - cmd := "agent Octa terminate" - res, err := cli.RunCommand(ctx, "agent Octa terminate") - if err != nil { - t.Errorf("error executing command %q:\n%v", cmd, err) - } - if res.Error() != "" { - t.Errorf("error executing command %q:\n%v", cmd, res.Error()) - } - } dutOcRoot := &oc.Root{} rp := dutOcRoot.GetOrCreateRoutingPolicy() ds := rp.GetOrCreateDefinedSets() - v4PrefixSet := ds.GetOrCreatePrefixSet("TAG_3_IPV4") - v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + v4PrefixSet := ds.GetOrCreatePrefixSet(tag3IPv4) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + v4PrefixSet.GetOrCreatePrefix("10.240.31.48/28", mskLen) + v4PrefixSet.GetOrCreatePrefix("10.244.187.32/28", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.36.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.37.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.38.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.39.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.40.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.41.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.42.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.43.128.0/20", mskLen) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).Config(), v4PrefixSet) + prefixSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).State()) + if got, want := len(prefixSet.Prefix), 10; got != want { + t.Errorf("Prefix set has %v prefixes, want %v", got, want) + } + + gnoi.KillProcess(t, dut, gnoi.OCAGENT, true) + + v4PrefixSet = ds.GetOrCreatePrefixSet(tag3IPv4) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } v4PrefixSet.GetOrCreatePrefix("173.49.128.0/20", mskLen) v4PrefixSet.GetOrCreatePrefix("173.46.128.0/20", mskLen) v4PrefixSet.GetOrCreatePrefix("10.240.31.48/28", mskLen) @@ -172,9 +162,9 @@ func TestPrefixSetWithCLIConfig(t *testing.T) { v4PrefixSet.GetOrCreatePrefix("173.48.128.0/20", mskLen) v4PrefixSet.GetOrCreatePrefix("173.45.128.0/20", mskLen) - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet("TAG_3_IPV4").Config(), v4PrefixSet) - prefixSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet("TAG_3_IPV4").State()) - if len(prefixSet.Prefix) != 22 { - t.Errorf("Prefix set has %v prefixes, want 22", len(prefixSet.Prefix)) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).Config(), v4PrefixSet) + prefixSet = gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).State()) + if got, want := len(prefixSet.Prefix), 22; got != want { + t.Errorf("Prefix set has %v prefixes, want %v", got, want) } } diff --git a/feature/gnoi/system/tests/copying_debug_files_test/copying_debug_files_test.go b/feature/gnoi/system/tests/copying_debug_files_test/copying_debug_files_test.go index 5fe2605d143..39c752d9f74 100644 --- a/feature/gnoi/system/tests/copying_debug_files_test/copying_debug_files_test.go +++ b/feature/gnoi/system/tests/copying_debug_files_test/copying_debug_files_test.go @@ -19,11 +19,11 @@ import ( "time" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/system" hpb "github.com/openconfig/gnoi/healthz" spb "github.com/openconfig/gnoi/system" tpb "github.com/openconfig/gnoi/types" "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" ) var ( @@ -53,7 +53,7 @@ func TestMain(m *testing.M) { // DUT // // Test notes: -//. Note: Initiating checkin to experimental +// Note: Initiating checkin to experimental // - KillProcess system call is used to kill a process. // - The healthz call needs to be modified to reflect the right component and its path. // @@ -67,10 +67,14 @@ func TestCopyingDebugFiles(t *testing.T) { if _, ok := processName[dut.Vendor()]; !ok { t.Fatalf("Please add support for vendor %v in var processName", dut.Vendor()) } + pID := system.FindProcessIDByName(t, dut, processName[dut.Vendor()]) + if pID == 0 { + t.Fatalf("process %v not found on device", processName[dut.Vendor()]) + } killProcessRequest := &spb.KillProcessRequest{ Signal: spb.KillProcessRequest_SIGNAL_KILL, Name: processName[dut.Vendor()], - Pid: findProcessByName(context.Background(), t, dut, processName[dut.Vendor()]), + Pid: uint32(pID), Restart: true, } processKillResponse, err := gnoiClient.System().KillProcess(context.Background(), killProcessRequest) @@ -109,19 +113,6 @@ func TestCopyingDebugFiles(t *testing.T) { } } -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, pName string) uint32 { - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint32 - for _, proc := range pList { - if proc.GetName() == pName { - pID = uint32(proc.GetPid()) - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } - } - return pID -} - func TestChassisComponentArtifacts(t *testing.T) { dut := ondatra.DUT(t, "dut") gnoiClient := dut.RawAPIs().GNOI(t) @@ -153,10 +144,10 @@ func TestChassisComponentArtifacts(t *testing.T) { t.Logf("Artifacts received for component %v: %v", componentName["name"], artifacts) // Fetch artifact details by executing ArtifactRequest and passing the artifact ID along. for _, artifact := range artifacts { - artId := artifact.GetId() - t.Logf("Executing ArtifactRequest for artifact ID %v", artId) + artID := artifact.GetId() + t.Logf("Executing ArtifactRequest for artifact ID %v", artID) artReq := &hpb.ArtifactRequest{ - Id: artId, + Id: artID, } // Verify that a valid response is received. artRes, err := gnoiClient.Healthz().Artifact(context.Background(), artReq) @@ -164,9 +155,9 @@ func TestChassisComponentArtifacts(t *testing.T) { t.Fatalf("Unexpected error on executing Healthz Artifact RPC: %v", err) } h1, err := artRes.Header() - t.Logf("Header of artifact %v: %v", artId, h1) + t.Logf("Header of artifact %v: %v", artID, h1) if err != nil { - t.Fatalf("Unexpected error when fetching the header of artifact %v: %v", artId, err) + t.Fatalf("Unexpected error when fetching the header of artifact %v: %v", artID, err) } } } diff --git a/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go b/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go index d886e79bc47..1792e620550 100644 --- a/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go +++ b/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go @@ -331,7 +331,7 @@ func addNHGReferencingToDownPort(tcArgs *testArgs, wantACK fluent.ProgrammingRes tcArgs.client.Modify().AddEntry(t, fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). - WithIndex(2000).WithInterfaceRef(p.Name()).WithIPAddress(atePort2.IPv4), + WithIndex(2000).WithIPAddress(atePort2.IPv4), fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). WithID(2000).AddNextHop(2000, 1), fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). diff --git a/feature/gribi/otg_tests/supervisor_failure_test/supervisor_failure_test.go b/feature/gribi/otg_tests/supervisor_failure_test/supervisor_failure_test.go index 634a45c0de6..4dd22321943 100644 --- a/feature/gribi/otg_tests/supervisor_failure_test/supervisor_failure_test.go +++ b/feature/gribi/otg_tests/supervisor_failure_test/supervisor_failure_test.go @@ -220,9 +220,10 @@ func findSecondaryController(t *testing.T, dut *ondatra.DUTDevice, controllers [ } // validateTelemetry validates telemetry sensors -func validateTelemetry(t *testing.T, dut *ondatra.DUTDevice, primaryAfterSwitch string) { +func validateTelemetry(t *testing.T, dut *ondatra.DUTDevice, primaryAfterSwitch, secondaryAfterSwitch string) { t.Log("Validate OC Switchover time/reason.") primary := gnmi.OC().Component(primaryAfterSwitch) + secondary := gnmi.OC().Component(secondaryAfterSwitch) if !gnmi.Lookup(t, dut, primary.LastSwitchoverTime().State()).IsPresent() { t.Errorf("primary.LastSwitchoverTime().Lookup(t).IsPresent(): got false, want true") } else { @@ -244,16 +245,16 @@ func validateTelemetry(t *testing.T, dut *ondatra.DUTDevice, primaryAfterSwitch t.Errorf("primary.GetLastSwitchoverReason().GetTrigger(): got %s, want %s.", got, want) } - if !gnmi.Lookup(t, dut, primary.LastRebootTime().State()).IsPresent() { - t.Errorf("primary.LastRebootTime.().Lookup(t).IsPresent(): got false, want true") + if !gnmi.Lookup(t, dut, secondary.LastRebootTime().State()).IsPresent() { + t.Errorf("secondary.LastRebootTime.().Lookup(t).IsPresent(): got false, want true") } else { - lastrebootTime := gnmi.Get(t, dut, primary.LastRebootTime().State()) + lastrebootTime := gnmi.Get(t, dut, secondary.LastRebootTime().State()) t.Logf("Found lastRebootTime.GetDetails(): %v", lastrebootTime) } - if !gnmi.Lookup(t, dut, primary.LastRebootReason().State()).IsPresent() { - t.Errorf("primary.LastRebootReason.().Lookup(t).IsPresent(): got false, want true") + if !gnmi.Lookup(t, dut, secondary.LastRebootReason().State()).IsPresent() { + t.Errorf("secondary.LastRebootReason.().Lookup(t).IsPresent(): got false, want true") } else { - lastrebootReason := gnmi.Get(t, dut, primary.LastRebootReason().State()) + lastrebootReason := gnmi.Get(t, dut, secondary.LastRebootReason().State()) t.Logf("Found lastRebootReason.GetDetails(): %v", lastrebootReason) } } @@ -354,8 +355,8 @@ func TestSupFailure(t *testing.T) { // Old secondary controller becomes primary after switchover. primaryAfterSwitch := secondaryBeforeSwitch - - validateTelemetry(t, dut, primaryAfterSwitch) + secondaryAfterSwitch := secondaryBeforeSwitch + validateTelemetry(t, dut, primaryAfterSwitch, secondaryAfterSwitch) // Assume Controller Switchover happened, ensure traffic flows without loss. // Verify the entry for 203.0.113.0/24 is active through AFT Telemetry. // Try starting the gribi client twice as switchover may reset the connection. diff --git a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go index 81157a8e260..48cacf68dd8 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go +++ b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go @@ -176,13 +176,15 @@ func TestAggregateAllNotForwardingViable(t *testing.T) { changeMetric(t, dut, aggIDs[2], 30) top := configureATE(t, ate) installGRIBIRoutes(t, dut, ate, top) - flows := createFlows(t, dut, top, aggIDs) ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) for _, aggID := range aggIDs { - gnmi.Await(t, dut, gnmi.OC().Interface(aggID).OperStatus().State(), 30*time.Second, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(aggID).OperStatus().State(), 60*time.Second, oc.Interface_OperStatus_UP) } + flows := createFlows(t, ate, top) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) for _, agg := range []*aggPortData{agg1, agg2, agg3} { bgpPath := ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() gnmi.Await(t, dut, bgpPath.Neighbor(agg.ateIPv4).SessionState().State(), time.Minute, oc.Bgp_Neighbor_SessionState_ESTABLISHED) @@ -215,7 +217,9 @@ func TestAggregateAllNotForwardingViable(t *testing.T) { // Ensure ISIS Adjacency is Down on LAG_2 if ok := awaitAdjacency(t, dut, aggIDs[1], oc.Isis_IsisInterfaceAdjState_DOWN); !ok { - t.Fatal("ISIS Adjacency is Established on LAG_2") + if presence := gnmi.LookupAll(t, dut, ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis().Interface(aggIDs[1]).LevelAny().AdjacencyAny().AdjacencyState().State()); len(presence) > 0 { + t.Fatalf("ISIS Adjacency is Established on LAG_2 ") + } } startTraffic(t, dut, ate, top) if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount+1], dutPortList[1:agg2.ateLagCount+1]); err != nil { @@ -292,7 +296,9 @@ func TestAggregateAllNotForwardingViable(t *testing.T) { configForwardingViable(t, dut, dutPortList[1:2], false) // Ensure ISIS Adjacency is Down on LAG_2 if ok := awaitAdjacency(t, dut, aggIDs[1], oc.Isis_IsisInterfaceAdjState_DOWN); !ok { - t.Fatal("ISIS Adjacency is Established on LAG_2") + if presence := gnmi.LookupAll(t, dut, ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis().Interface(aggIDs[1]).LevelAny().AdjacencyAny().AdjacencyState().State()); len(presence) > 0 { + t.Fatalf("ISIS Adjacency is Established on LAG_2") + } } startTraffic(t, dut, ate, top) if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:(agg2.ateLagCount+1)], dutPortList[1:(agg2.ateLagCount+1)]); err != nil { @@ -394,7 +400,7 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) []string { configureRoutingPolicy(t, dut) configureDUTISIS(t, dut, aggIDs) - if deviations.MaxEcmpPaths(dut) { + if !deviations.MaxEcmpPaths(dut) { isisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() gnmi.Update(t, dut, isisPath.Global().MaxEcmpPaths().Config(), 2) } @@ -805,9 +811,9 @@ func incrementMAC(mac string, i int) (string, error) { return newMac.String(), nil } -func createFlows(t *testing.T, dut *ondatra.DUTDevice, top gosnappi.Config, aggIDs []string) []gosnappi.Flow { - for _, aggID := range aggIDs { - dutAggMac = append(dutAggMac, gnmi.Get(t, dut, gnmi.OC().Lacp().Interface(aggID).SystemIdMac().State())) +func createFlows(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []gosnappi.Flow { + for _, aggID := range []*aggPortData{agg1, agg2, agg3} { + dutAggMac = append(dutAggMac, gnmi.Get(t, ate.OTG(), gnmi.OTG().Interface(aggID.ateAggName+".Eth").Ipv4Neighbor(aggID.dutIPv4).LinkLayerAddress().State())) } f1V4 := configureFlows(t, top, pfx1AdvV4, pfx2AdvV4, "pfx1ToPfx2_3", agg1, []*aggPortData{agg2, agg3}, dutAggMac[0], ipRange[1]) f2V4 := configureFlows(t, top, pfx1AdvV4, pfx4AdvV4, "pfx1ToPfx4", agg1, []*aggPortData{agg2, agg3}, dutAggMac[0], ipRange[0]) @@ -958,7 +964,7 @@ func awaitAdjacency(t *testing.T, dut *ondatra.DUTDevice, intfName string, state isisPath := ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() intf := isisPath.Interface(intfName) query := intf.LevelAny().AdjacencyAny().AdjacencyState().State() - _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + _, ok := gnmi.WatchAll(t, dut, query, 90*time.Second, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { v, ok := val.Val() return v == state && ok }).Await(t) diff --git a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto index 71c189d4df0..0896d869a61 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto +++ b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto @@ -36,7 +36,7 @@ platform_exceptions: { static_protocol_name: "STATIC" aggregate_atomic_update: true missing_value_for_defaults: true - max_ecmp_paths: false + max_ecmp_paths: true explicit_interface_in_default_vrf: false } } diff --git a/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md b/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md index 6e0e134a35d..e40267537ed 100644 --- a/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md +++ b/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md @@ -17,29 +17,31 @@ Determine LLDP advertisement and reception operates correctly. configuration of lldp/interfaces/interface/config/enabled (TRUE or FALSE) on any interface. -## Config Parameter coverage - -* /lldp/config/enabled -* /lldp/interfaces/interface/config/enabled - -## Telemetry Parameter coverage - -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/system-name -* /lldp/interfaces/interface/state/name -* /lldp/state/chassis-id -* /lldp/state/chassis-id-type -* /lldp/state/system-name - -## Protocol/RPC Parameter coverage - -LLDP: - -* /lldp/config/enabled = true -* /lldp/interfaces/interface/config/enabled = true +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. + +```yaml +paths: + ## Config Paths ## + /lldp/config/enabled: + /lldp/interfaces/interface/config/enabled: + + ## State Paths ## + /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id: + /lldp/interfaces/interface/neighbors/neighbor/state/port-id: + /lldp/interfaces/interface/neighbors/neighbor/state/system-name: + /lldp/interfaces/interface/state/name: + /lldp/state/chassis-id: + /lldp/state/chassis-id-type: + /lldp/state/system-name: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + +``` ## Minimum DUT platform requirement diff --git a/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go b/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go index 61532d41ea1..73498534b12 100644 --- a/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go +++ b/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go @@ -94,19 +94,21 @@ func TestCoreLLDPTLVPopulation(t *testing.T) { func configureNode(t *testing.T, name string, lldpEnabled bool) (*ondatra.DUTDevice, *oc.Lldp) { node := ondatra.DUT(t, name) p := node.Port(t, portName) - lldp := gnmi.OC().Lldp() + d := &oc.Root{} + lldp := d.GetOrCreateLldp() + llint := lldp.GetOrCreateInterface(p.Name()) - gnmi.Replace(t, node, lldp.Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Enabled().Config(), lldpEnabled) if lldpEnabled { - gnmi.Replace(t, node, lldp.Interface(p.Name()).Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Interface(p.Name()).Config(), llint) } if deviations.InterfaceEnabled(node) { gnmi.Replace(t, node, gnmi.OC().Interface(p.Name()).Enabled().Config(), true) } - return node, gnmi.Get(t, node, lldp.Config()) + return node, gnmi.Get(t, node, gnmi.OC().Lldp().Config()) } // verifyNodeConfig verifies the config by comparing against the telemetry state object. diff --git a/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md b/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md index 6e0e134a35d..e40267537ed 100644 --- a/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md +++ b/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md @@ -17,29 +17,31 @@ Determine LLDP advertisement and reception operates correctly. configuration of lldp/interfaces/interface/config/enabled (TRUE or FALSE) on any interface. -## Config Parameter coverage - -* /lldp/config/enabled -* /lldp/interfaces/interface/config/enabled - -## Telemetry Parameter coverage - -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/system-name -* /lldp/interfaces/interface/state/name -* /lldp/state/chassis-id -* /lldp/state/chassis-id-type -* /lldp/state/system-name - -## Protocol/RPC Parameter coverage - -LLDP: - -* /lldp/config/enabled = true -* /lldp/interfaces/interface/config/enabled = true +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. + +```yaml +paths: + ## Config Paths ## + /lldp/config/enabled: + /lldp/interfaces/interface/config/enabled: + + ## State Paths ## + /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id: + /lldp/interfaces/interface/neighbors/neighbor/state/port-id: + /lldp/interfaces/interface/neighbors/neighbor/state/system-name: + /lldp/interfaces/interface/state/name: + /lldp/state/chassis-id: + /lldp/state/chassis-id-type: + /lldp/state/system-name: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + +``` ## Minimum DUT platform requirement diff --git a/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go b/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go index 532595c9e28..e34e217dcd9 100644 --- a/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go +++ b/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go @@ -138,18 +138,21 @@ func TestLLDPDisabled(t *testing.T) { func configureDUT(t *testing.T, name string, lldpEnabled bool) (*ondatra.DUTDevice, *oc.Lldp) { node := ondatra.DUT(t, name) p := node.Port(t, portName) - lldp := gnmi.OC().Lldp() + d := &oc.Root{} + lldp := d.GetOrCreateLldp() + llint := lldp.GetOrCreateInterface(p.Name()) - gnmi.Replace(t, node, lldp.Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Enabled().Config(), lldpEnabled) if lldpEnabled { - gnmi.Replace(t, node, lldp.Interface(p.Name()).Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Interface(p.Name()).Config(), llint) } + if deviations.InterfaceEnabled(node) { gnmi.Replace(t, node, gnmi.OC().Interface(p.Name()).Enabled().Config(), true) } - return node, gnmi.Get(t, node, lldp.Config()) + return node, gnmi.Get(t, node, gnmi.OC().Lldp().Config()) } func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { diff --git a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md index c9ad228379e..4719f77f44b 100644 --- a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md +++ b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md @@ -23,13 +23,16 @@ Ensure that data plane traffic is not interrupted by P4RT daemon failure. test tables only configure the control plane traffic. Instead, this test configures the data plane using gRIBI. -## Protocol/RPC Parameter Coverage - -* gRIBI - * ModifyRequest - * GetRequest +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Telemetry Parameter Coverage * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix/ * /interfaces/interface/state/id -* /interfaces/interface/state/name \ No newline at end of file +* /interfaces/interface/state/name diff --git a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go index 239ea5ba306..e0b93542926 100644 --- a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go +++ b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go @@ -15,7 +15,6 @@ package p4rt_daemon_failure_test import ( - "context" "fmt" "testing" "time" @@ -24,6 +23,7 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/p4rtutils" "github.com/openconfig/gribigo/fluent" @@ -34,7 +34,6 @@ import ( "github.com/openconfig/ygot/ygot" gpb "github.com/openconfig/gnmi/proto/gnmi" - syspb "github.com/openconfig/gnoi/system" ) func TestMain(m *testing.M) { @@ -80,13 +79,6 @@ var ( IPv4: "192.0.2.6", IPv4Len: ipv4PrefixLen, } - - p4rtDaemons = map[ondatra.Vendor]string{ - ondatra.ARISTA: "P4Runtime", - ondatra.CISCO: "emsd", - ondatra.JUNIPER: "p4-switch", - ondatra.NOKIA: "sr_grpc_server", - } ) // configInterfaceDUT returns the OC Interface config for a given port. @@ -192,18 +184,6 @@ func startTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) gos return flow } -// pidByName uses telemetry to find out the PID of a process -func pidByName(t *testing.T, dut *ondatra.DUTDevice, process string) (uint64, error) { - t.Helper() - ps := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - for _, p := range ps { - if p.GetName() == process { - return p.GetPid(), nil - } - } - return 0, fmt.Errorf("could not find PID for process: %s", process) -} - func installRoutes(t *testing.T, dut *ondatra.DUTDevice) error { t.Helper() @@ -299,11 +279,6 @@ func subscribeOnChangeInterfaceName(t *testing.T, dut *ondatra.DUTDevice, p *ond func TestP4RTDaemonFailure(t *testing.T) { dut := ondatra.DUT(t, "dut") - p4rtD, ok := p4rtDaemons[dut.Vendor()] - if !ok { - t.Fatalf("Please add support for vendor %v in var p4rtDaemons", dut.Vendor()) - } - t.Logf("Configure DUT") configureDUT(t, dut) @@ -334,23 +309,7 @@ func TestP4RTDaemonFailure(t *testing.T) { flow := startTraffic(t, ate, top) - pID, err := pidByName(t, dut, p4rtD) - if err != nil { - t.Fatal(err) - } - - c := dut.RawAPIs().GNOI(t) - req := &syspb.KillProcessRequest{ - Name: p4rtD, - Pid: uint32(pID), - Signal: syspb.KillProcessRequest_SIGNAL_TERM, - Restart: true, - } - resp, err := c.System().KillProcess(context.Background(), req) - t.Logf("Got kill process response: %v", resp) - if err != nil { - t.Fatalf("FAIL: to execute gNOI.KillProcess, error received: %v", err) - } + gnoi.KillProcess(t, dut, gnoi.P4RT, false) // let traffic keep running for another 10 seconds. time.Sleep(10 * time.Second) diff --git a/feature/platform/tests/telemetry_inventory_test/metadata.textproto b/feature/platform/tests/telemetry_inventory_test/metadata.textproto index 2f00560c835..74c89711589 100644 --- a/feature/platform/tests/telemetry_inventory_test/metadata.textproto +++ b/feature/platform/tests/telemetry_inventory_test/metadata.textproto @@ -40,7 +40,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - backplane_facing_capacity_unsupported: true install_position_and_install_component_unsupported: true model_name_unsupported: true skip_controller_card_power_admin: true diff --git a/feature/qos/tests/qos_ecn_config_test/README.md b/feature/qos/tests/qos_ecn_config_test/README.md index 025b4b5a595..adb5a3f2d79 100644 --- a/feature/qos/tests/qos_ecn_config_test/README.md +++ b/feature/qos/tests/qos_ecn_config_test/README.md @@ -60,7 +60,7 @@ Verify QoS ECN feature configuration. * ECN * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold-percent - * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold-percent + * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold-percent * qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/config/enable-ecn @@ -79,7 +79,7 @@ Verify QoS ECN feature configuration. * ECN * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold-percent - * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold-percent + * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold-percent * qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/state/enable-ecn @@ -96,3 +96,39 @@ Verify QoS ECN feature configuration. ## platform * vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/enable-ecn: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/weight: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/drop: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-drop-probability-percent: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/output/queues/queue/config/queue-management-profile: + + ## State paths: + + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/enable-ecn: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/weight: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/drop: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-drop-probability-percent: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/output/queues/queue/state/queue-management-profile: + +rpcs: + gnmi: + gNMI.Set: + Replace: +``` \ No newline at end of file diff --git a/feature/qos/tests/qos_ecn_config_test/metadata.textproto b/feature/qos/tests/qos_ecn_config_test/metadata.textproto index 729421003ce..2a2d548b8bb 100644 --- a/feature/qos/tests/qos_ecn_config_test/metadata.textproto +++ b/feature/qos/tests/qos_ecn_config_test/metadata.textproto @@ -9,10 +9,6 @@ platform_exceptions: { platform: { vendor: JUNIPER } - deviations: { - state_path_unsupported: true - drop_weight_leaves_unsupported: true - } } platform_exceptions: { platform: { diff --git a/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go b/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go index 0e9f12ffedf..ae94ee7fb04 100644 --- a/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go +++ b/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go @@ -302,46 +302,114 @@ func testQoSOutputIntfConfig(t *testing.T, q *oc.Qos) { dp := dut.Port(t, "port2") queues := netutil.CommonTrafficQueues(t, dut) + ecnConfig := struct { + ecnEnabled bool + dropEnabled bool + minThreshold uint64 + maxThreshold uint64 + maxDropProbabilityPercent uint8 + weight uint32 + }{ + ecnEnabled: true, + dropEnabled: false, + minThreshold: uint64(80000), + maxThreshold: uint64(80000), + maxDropProbabilityPercent: uint8(100), + weight: uint32(0), + } + + queueMgmtProfile := q.GetOrCreateQueueManagementProfile("DropProfile") + queueMgmtProfile.SetName("DropProfile") + wred := queueMgmtProfile.GetOrCreateWred() + uniform := wred.GetOrCreateUniform() + uniform.SetEnableEcn(ecnConfig.ecnEnabled) + uniform.SetDrop(ecnConfig.dropEnabled) + wantMinThreshold := ecnConfig.minThreshold + wantMaxThreshold := ecnConfig.maxThreshold + if deviations.EcnSameMinMaxThresholdUnsupported(dut) { + wantMinThreshold = CiscoMinThreshold + wantMaxThreshold = CiscoMaxThreshold + } + uniform.SetMinThreshold(wantMinThreshold) + uniform.SetMaxThreshold(wantMaxThreshold) + uniform.SetMaxDropProbabilityPercent(ecnConfig.maxDropProbabilityPercent) + if !deviations.QosSetWeightConfigUnsupported(dut) { + uniform.SetWeight(ecnConfig.weight) + } + cases := []struct { desc string queueName string ecnProfile string scheduler string + sequence uint32 + priority oc.E_Scheduler_Priority + inputID string + inputType oc.E_Input_InputType + weight uint64 }{{ desc: "output-interface-BE1", queueName: queues.BE1, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(1), }, { desc: "output-interface-BE0", queueName: queues.BE0, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(4), }, { desc: "output-interface-AF1", queueName: queues.AF1, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(8), }, { desc: "output-interface-AF2", queueName: queues.AF2, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(16), }, { desc: "output-interface-AF3", queueName: queues.AF3, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(32), }, { desc: "output-interface-AF4", queueName: queues.AF4, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(0), + priority: oc.Scheduler_Priority_STRICT, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(6), }, { desc: "output-interface-NC1", queueName: queues.NC1, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(0), + priority: oc.Scheduler_Priority_STRICT, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(7), }} i := q.GetOrCreateInterface(dp.Name()) @@ -366,6 +434,14 @@ func testQoSOutputIntfConfig(t *testing.T, q *oc.Qos) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { qoscfg.SetForwardingGroup(t, dut, q, tc.queueName, tc.queueName) + s := schedulerPolicy.GetOrCreateScheduler(tc.sequence) + s.SetSequence(tc.sequence) + s.SetPriority(tc.priority) + input := s.GetOrCreateInput(tc.queueName) + input.SetId(tc.queueName) + input.SetInputType(tc.inputType) + input.SetQueue(tc.queueName) + input.SetWeight(tc.weight) output := i.GetOrCreateOutput() schedulerPolicy := output.GetOrCreateSchedulerPolicy() schedulerPolicy.SetName(tc.scheduler) diff --git a/internal/gnoi/gnoi.go b/internal/gnoi/gnoi.go new file mode 100644 index 00000000000..f308fb956d0 --- /dev/null +++ b/internal/gnoi/gnoi.go @@ -0,0 +1,124 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gnoi provides utilities for interacting with the gNOI API. +package gnoi + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/system" + gpb "github.com/openconfig/gnmi/proto/gnmi" + spb "github.com/openconfig/gnoi/system" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +var ( + daemonProcessNames = map[ondatra.Vendor]map[Daemon]string{ + ondatra.ARISTA: { + GRIBI: "Gribi", + OCAGENT: "Octa", + P4RT: "P4Runtime", + ROUTING: "Bgp-main", + }, + ondatra.CISCO: { + GRIBI: "emsd", + P4RT: "emsd", + ROUTING: "emsd", + }, + ondatra.JUNIPER: { + GRIBI: "rpd", + P4RT: "p4-switch", + ROUTING: "rpd", + }, + ondatra.NOKIA: { + GRIBI: "sr_grpc_server", + OCAGENT: "oc_mgmt_server", + P4RT: "sr_grpc_server", + ROUTING: "sr_bgp_mgr", + }, + } +) + +// Daemon is the type of the daemon on the device. +type Daemon string + +const ( + // GRIBI is the gRIBI daemon. + GRIBI Daemon = "GRIBI" + // OCAGENT is the OpenConfig agent daemon. + OCAGENT Daemon = "OCAGENT" + // P4RT is the P4RT daemon. + P4RT Daemon = "P4RT" + // ROUTING is the routing daemon. + ROUTING Daemon = "ROUTING" +) + +// KillProcess terminates the daemon on the DUT. +func KillProcess(t *testing.T, dut *ondatra.DUTDevice, daemon Daemon, waitForRestart bool) { + t.Helper() + + daemonName, err := FetchProcessName(dut, daemon) + if err != nil { + t.Fatalf("Daemon %s not defined for vendor %s", daemon, dut.Vendor().String()) + } + pid := system.FindProcessIDByName(t, dut, daemonName) + if pid == 0 { + t.Fatalf("process %s not found on device", daemonName) + } + + gnoiClient := dut.RawAPIs().GNOI(t) + killProcessRequest := &spb.KillProcessRequest{ + Signal: spb.KillProcessRequest_SIGNAL_KILL, + Name: daemonName, + Pid: uint32(pid), + Restart: true, + } + gnoiClient.System().KillProcess(context.Background(), killProcessRequest) + + if waitForRestart { + gnmi.WatchAll( + t, + dut.GNMIOpts().WithYGNMIOpts(ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_ON_CHANGE)), + gnmi.OC().System().ProcessAny().State(), + time.Minute, + func(p *ygnmi.Value[*oc.System_Process]) bool { + val, ok := p.Val() + if !ok { + return false + } + return val.GetName() == daemonName && val.GetPid() != pid + }, + ) + } +} + +// FetchProcessName returns the name of the daemon on the DUT based on the vendor. +func FetchProcessName(dut *ondatra.DUTDevice, daemon Daemon) (string, error) { + daemons, ok := daemonProcessNames[dut.Vendor()] + if !ok { + return "", fmt.Errorf("unsupported vendor: %s", dut.Vendor().String()) + } + d, ok := daemons[daemon] + if !ok { + return "", fmt.Errorf("daemon %s not defined for vendor %s", daemon, dut.Vendor().String()) + } + return d, nil +} diff --git a/internal/system/system.go b/internal/system/system.go new file mode 100644 index 00000000000..d21116e2750 --- /dev/null +++ b/internal/system/system.go @@ -0,0 +1,39 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package system provides helper functions for gNMI system related operations. +package system + +import ( + "testing" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +// FindProcessIDByName uses telemetry to find out the PID of a process. +func FindProcessIDByName(t *testing.T, dut *ondatra.DUTDevice, pName string) uint64 { + t.Helper() + + var pid uint64 + pList := gnmi.GetAll[*oc.System_Process](t, dut, gnmi.OC().System().ProcessAny().State()) + for _, proc := range pList { + if proc.GetName() == pName { + pid = proc.GetPid() + break + } + } + return pid +}